从零开始的c++之旅——二叉搜索树
1、二叉搜索树概念
1. ⼆叉搜索树的概念
⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树:
• 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值
• 若它的右⼦树不为空,则右⼦树上所有结点的值都⼤于等于根结点的值
• 它的左右⼦树也分别为⼆叉搜索树
• ⼆叉搜索树中可以⽀持插⼊相等的值,也可以不⽀持插⼊相等的值,具体看使⽤场景定义,map/set/multimap/multiset系列容器底层就是⼆叉搜索树,其中map/set不⽀持插⼊相等 值,multimap/multiset⽀持插⼊相等值
以下就是两颗二叉搜索树
2. ⼆叉搜索树的性能分析
最优情况下,⼆叉搜索树为完全⼆叉树(或者接近完全⼆叉树),其⾼度为:O(log2 N)
最差情况下,⼆叉搜索树退化为单⽀树(或者类似单⽀),其⾼度为:O( N/2 )
所以综合⽽⾔⼆叉搜索树增删查改时间复杂度为:O(N)
这样的效率显然是⽆法满⾜我们需求的,后续会讲解⼆叉搜索树的变形,平衡⼆ 叉搜索树AVL树和红⿊树,才能适⽤于我们在内存中存储和搜索数据。
⼆分查找也可以实现O( logN ) 级别的查找效率,但是⼆分查找有两⼤缺陷:
1. 需要存储在⽀持下标随机访问的结构中,并且有序。
2. 插⼊和删除数据效率很低,因为存储在下标随机访问的结构中,插⼊和删除数据⼀般需要挪动数据。
这⾥也就体现出了⼆叉搜索树的价值。
3. 二叉搜索树的实现
需要明确的一点是,我们此处实现的是不可插入相同值的二叉搜索树。
而搜索二叉树的本质还是使用递归来完成,因此与我们之前博客实现的二叉树逻辑大体相似,因此一些类似的操作我们简略描述。
3.1 定义二叉搜索树
3.1.2 定义二叉搜索树节点
这与之前实现的二叉树类似,只不过用上了模板跟构造函数,因为构造函数我们在后面需要用来生成节点。
template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};
3.1.2 定义二叉搜索树结构
template<class K>
class BSTree
{//这里也能体现封装思想,不管我们如何实现的类此处我们只需定义成Node即可typedef BSTNode<K> Node; private:Node* _root = nullptr;//头节点
}
3.2 中序遍历
根据二叉搜索树的性质,中序遍历得到的应该是一个有序的升序列表。
中序搜索的逻辑与之前大差不差,我们只需记住:先遍历左子树,在打印当前根节点的值,而后遍历右子树,这就是“ 左 根 右”。
不要忘记了返回条件:遍历到空需要返回到上一级。
最后需要注意的一点是,我们遍历时需要传入头节点root,但是root是我们类的私有成员变量,在类外无法访问,那我们怎么办呢?对于这种问题有个统一的办法,我们可以将这个函数定义成类的私有成员,在写一个类的公有成员函数,去调用类的私有成员函数即可。
pubilc:void InOder(){_InOder(_root);cout << endl;} private:void _InOder(Node* root){if (root == nullptr){return;}_InOder(root->_left);cout << root->_key << " ";_InOder(root->_right);}
3.3 插入
我们根据搜索二叉树的特点可以得出我们的插入逻辑:
1.若当前的树是一颗空树,那么我们只需新增节点赋给root节点即可。
2.若树不为空我们只需根据二叉搜索树的性质,定义一个指针cur遍历二叉搜索树,若cur指向的节点值大于我们要插入的值va,则cur往右子树走,反之则往左子树走,知道cur的下一节点为空时,用val初始化一个节点并根据cur和val的值比较,判断插入到左子树还是右子树
bool Insert(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = _root;Node* cur = _root;while (cur){if ( val < cur->_key ){parent = cur;cur = cur->_left;}else if ( val > cur->_key ){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(val);if (val > parent->_key){parent->_right = cur;}else{parent->_left= cur ;}return true;}
3.4 查找
从根开始⽐较,查找x,x⽐根的值⼤则往右边⾛查找,x⽐根值⼩则往左边⾛查找。⾛到到空,还没找到,这个值不存在,返回false。找到则返回true。
bool Find(const K& val){Node* cur = _root;while (cur){if (val > cur->_key){cur = cur->_right;}else if (val < cur->_key){cur = cur->_left;}else{return true;}}return false;}
3.5 删除
二叉搜索树的插入时整个步骤中最复杂的,因为在插入数据时我们需要对二叉搜索树的部分结构进行适当的调整,使其保持原有的性质。
我们以下图的删除二叉数为例
在删除之前,我们还需要明确一点,我们删除节点释放完资源之后,还需使其对应的父亲节点指向nullptr,避免产生野指针的问题,因此我们我们要一个指针cur来搜寻要删除的节点,还需要一个指针parent来记录cur的前一节点。
在删除的过程中,分为了好几种情况。,我们需要分别讨论
1. 删除的位置为叶子节点
这时候只需要cur搜索到对应的节点,再删除即可。
2. 删除的位置只有一个孩子节点。
若cur为parent的左节点,则使 parent->left 指向cur的非空节点
反之则使 parent->right 指向cur的非空节点
3.删除位置有两个孩子节点
这是最后一种也是最复杂的一种情况。有两个孩子意味着删除了该节点后我们需要对搜索二叉树部分结构进行适当的调整。
这里我没有两种方法:我们定义一个指针MaxLeft,使其找到要删除节点cur左子树的最大值,或者定义一个指针MinRight,使其找到cur右子树的最小节点,将其与cur的值进行交换,在删除LeftMax或者MinRight指向的节点即可
为什么可以选择这两个节点的其中一个呢,因为只有选择这两个节点才会保证二叉搜索树的性质不变。
我们以交换右子树最小节点为例,下列图我们可以看出,MidRight指针与cur交换完之后二叉搜索树的性质依然符合。
但是我们还需要注意的是,和前面的两点一样,我们删除掉MidRight节点之后,不要文件将其父节点对应的指针指向nullptr,因此我们还需要定义一个指针PMidRight,防止释放完MidRight之后找不到其对应父节点。
以下是实现的代码
bool Erase(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (val < cur->_key){parent = cur;cur = cur->_left;}else if (val > cur->_key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){//让父亲指向我的右if (cur==parent->_right){parent->_right=cur->_right;}else{parent->_left = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}delete cur;return true;}else{Node* rightMin = cur->_right;Node* rightMinP = cur;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin->_right;elserightMinP->_right = rightMin->_right;delete rightMin;return true;}}}return false;}
4.源代码
#pragma once
#include<iostream>
#include<vector>
using namespace std;template<class K>
struct BSTNode
{K _key;BSTNode<K>* _left;BSTNode<K>* _right;BSTNode(const K& key):_key(key),_left(nullptr),_right(nullptr){}
};template<class K>
class BSTree
{typedef BSTNode<K> Node;public:bool Insert(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = _root;Node* cur = _root;while (cur){if ( val < cur->_key ){parent = cur;cur = cur->_left;}else if ( val > cur->_key ){parent = cur;cur = cur->_right;}else{return false;}}cur = new Node(val);if (val > parent->_key){parent->_right = cur;}else{parent->_left= cur ;}return true;}bool Find(const K& val){Node* cur = _root;while (cur){if (val > cur->_key){cur = cur->_right;}else if (val < cur->_key){cur = cur->_left;}else{return true;}}return false;}void InOder(){_InOder(_root);cout << endl;}bool Erase(const K& val){if (_root == nullptr){_root = new Node(val);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){if (val < cur->_key){parent = cur;cur = cur->_left;}else if (val > cur->_key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){//让父亲指向我的右if (cur==parent->_right){parent->_right=cur->_right;}else{parent->_left = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (cur == parent->_right){parent->_right = cur->_left;}else{parent->_left = cur->_left;}delete cur;return true;}else{Node* rightMin = cur->_right;Node* rightMinP = cur;while (rightMin->_left){rightMinP = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinP->_left == rightMin)rightMinP->_left = rightMin->_right;elserightMinP->_right = rightMin->_right;delete rightMin;return true;}}}return false;}private:Node* _root = nullptr;void _InOder(Node* root){if (root == nullptr){return;}_InOder(root->_left);cout << root->_key << " ";_InOder(root->_right);}
};
相关文章:

从零开始的c++之旅——二叉搜索树
1、二叉搜索树概念 1. ⼆叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树,它或者是⼀棵空树,或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空,则左⼦树上所有结点的值都⼩于等于根结点的值 • 若它的右⼦树不为空,则右⼦树上所有结…...

CSS回顾-基础知识详解
一、引言 在前端开发领域,CSS 曾是构建网页视觉效果的关键,与 HTML、JavaScript 一起打造精彩的网络世界。但随着组件库的大量涌现,我们亲手书写 CSS 样式的情况越来越少,CSS 基础知识也逐渐被我们遗忘。 现在,这种遗…...

Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别
Elasticsearch 查询时 term、match、match_phrase、match_phrase_prefix 的区别 keyword 与 text 区别term 查询match 查询match_phrase 查询match_phrase_prefix 查询写在最后 在讲述 es 查询时 term、match、match_phrase、match_phrase_prefix 的区别之前,先来了…...
低代码平台:跨数据库处理的重要性与实现方式
一、低代码平台概述 低代码平台作为一种创新的软件开发工具,为开发者带来了极大的便利。它具备可视化编程工具和大量预构建组件,这使得开发者无需编写大量代码就能创建应用程序,显著降低了软件开发的技术门槛。无论是专业开发人员还是业务人员…...
【jvm】如何破坏双亲委派机制
目录 1.说明2.重写ClassLoader的loadClass方法2.1 原理2.2 实现步骤2.3 注意事项 3.使用线程上下文类加载器3.1 原理3.2 实现步骤3.3 应用场景 4.利用SPI机制4.1 原理4.2 实现步骤4.3 应用场景 5.Tomcat等容器的自定义类加载器5.1 原理5.2 实现方式5.3 应用场景 1.说明 1.双亲委…...

ReactPress与WordPress:一场内容管理系统的较量
ReactPress Github项目地址:https://github.com/fecommunity/reactpress WordPress官网:https://wordpress.org/ ReactPress与WordPress:一场内容管理系统的较量 在当今数字化时代,内容管理系统(CMS)已成为…...

网络安全练习之 ctfshow_web
文章目录 VIP题目限免(即:信息泄露题)源码泄露前台JS绕过协议头信息泄露robots后台泄露phps源码泄露源码压缩包泄露版本控制泄露源码(git)版本控制泄露源码2(svn)vim临时文件泄露cookie泄露域名txt记录泄露敏感信息公布内部技术文档泄露编辑器…...
在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别
在 Service Worker 中,caches.put(request, response) 和 caches.add(request)/caches.addAll(requests) 方法都用于将资源添加到缓存中,但它们的使用场景和目的略有不同。 caches.put(request, response),一用在fetch事件当中,由…...

UNIAPP发布小程序调用讯飞在线语音合成+实时播报
语音合成能够将文字转化为自然流畅的人声,提供100发音人供您选择,支持多语种、多方言和中英混合,可灵活配置音频参数。广泛应用于新闻阅读、出行导航、智能硬件和通知播报等场景。 在当下大模型火爆的今日,语音交互页离不开语音合…...

跳房子(弱化版)
题目描述 跳房子,也叫跳飞机,是一种世界性的儿童游戏,也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下: 在地面上确定一个起点,然后在起点右侧画 n 个格子,这些格子都在同一条直线上。每个格子内…...
ubuntu22 安装 minikube
在Ubuntu 22上安装Minikube,你可以按照以下步骤进行: 安装依赖: 更新系统并安装必要的依赖项: sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl安装Docker: Minikube可以使用D…...
STM32 | 超声波避障小车
超声波避障小车 一、项目背题 由于超声波测距是一种非接触检测技术,不受光线、被测对象颜色等的影响,较其它仪器更卫生,更耐潮湿、粉尘、高温、腐蚀气体等恶劣环境,具有少维护、不污染、高可靠、长寿命等特点。因此可广泛应用于…...

打造旅游卡服务新标杆:构建SOP框架与智能知识库应用
随着旅游业的蓬勃兴起,旅游卡产品正逐渐成为市场的焦点。为了进一步提升服务质量和客户体验,构建一套高效且标准化的操作流程(SOP)变得尤为重要。本文将深入探讨如何构建旅游卡的SOP框架,并介绍如何利用智能知识库技术…...
通过脚本,发起分支合并请求和打tag
#!/bin/bash # Set GitLab API URL and access token GITLAB_API_URL"http://IP/api/v4" ACCESS_TOKEN"Token秘钥" # Define repository IDs declare -A repo_ids( ["gitIP:kingmq/client.git"]"123" ["gitIP:kingmq/s…...

【视频讲解】Python深度神经网络DNNs-K-Means(K-均值)聚类方法在MNIST等数据可视化对比分析...
全文链接:https://tecdat.cn/?p38289 分析师:Cucu Sun 近年来,由于诸如自动编码器等深度神经网络(DNN)的高表示能力,深度聚类方法发展迅速。其核心思想是表示学习和聚类可以相互促进:好的表示会…...
网络安全在线网站/靶场:全面探索与实践
目录 1. CyberPatriot 简介 功能与特点 适用人群 2. Hack The Box 简介 功能与特点 适用人群 3. OverTheWire 简介 功能与特点 适用人群 4. VulnHub 简介 功能与特点 适用人群 5. PortSwigger Web Security Academy 简介 功能与特点 适用人群 6. TryHackM…...
Ceph 中Crush 算法的理解
Crush(Controlled Replication Under Scalable Hashing)算法是一种可扩展的、分布式的副本数据放置算法,广泛用于存储系统中,特别是Ceph分布式存储系统中。以下是对CRUSH算法的详细解释: 一、算法原理 CRUSH算法根据…...

D70【 python 接口自动化学习】- python 基础之数据库
day70 Python综合实践 学习日期:20241116 学习目标: MySQL 数据库 Q -- Python 综合实践 学习笔记: 案例需求 数据内容 DDL定义 总结 1. 使用Python实现读取写入数据库操作 ps.今天去看航展了,歼20简直不要太快,明…...
C# LINQ数据访问技术
文章目录 1.LINQ 的基本概念1.1 LINQ 的优势1.2 LINQ 数据访问的方式 2.LINQ 基本操作2.1 查询语法2.2 方法语法 3.LINQ 常用查询方法3.1 Where3.2 Select3.3 OrderBy / OrderByDescending3.4 GroupBy3.5 Join3.6 Aggregate 4.LINQ 查询示例4.1 LINQ to Objects4.2 LINQ to SQL…...

【JavaSE线程知识总结】
多线程 一.创建线程1.多线程创建方式一(Thread)2.多线程创键方式二(Runnable)3.线程创建方式三 二.线程安全问题解决办法1.使用同步代码块synchornized 2 .使用Lock解决线程安全问题 三.总结 线程就是程序内部的一条执行流程 一.创建线程 常用的方法 Thread.currentThread()…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
AGain DB和倍数增益的关系
我在设置一款索尼CMOS芯片时,Again增益0db变化为6DB,画面的变化只有2倍DN的增益,比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析: 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...
苹果AI眼镜:从“工具”到“社交姿态”的范式革命——重新定义AI交互入口的未来机会
在2025年的AI硬件浪潮中,苹果AI眼镜(Apple Glasses)正在引发一场关于“人机交互形态”的深度思考。它并非简单地替代AirPods或Apple Watch,而是开辟了一个全新的、日常可接受的AI入口。其核心价值不在于功能的堆叠,而在于如何通过形态设计打破社交壁垒,成为用户“全天佩戴…...