当前位置: 首页 > news >正文

从零开始的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. ⼆叉搜索树的概念 ⼆叉搜索树⼜称⼆叉排序树&#xff0c;它或者是⼀棵空树&#xff0c;或者是具有以下性质的⼆叉树: • 若它的左⼦树不为空&#xff0c;则左⼦树上所有结点的值都⼩于等于根结点的值 • 若它的右⼦树不为空&#xff0c;则右⼦树上所有结…...

CSS回顾-基础知识详解

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

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 的区别之前&#xff0c;先来了…...

低代码平台:跨数据库处理的重要性与实现方式

一、低代码平台概述 低代码平台作为一种创新的软件开发工具&#xff0c;为开发者带来了极大的便利。它具备可视化编程工具和大量预构建组件&#xff0c;这使得开发者无需编写大量代码就能创建应用程序&#xff0c;显著降低了软件开发的技术门槛。无论是专业开发人员还是业务人员…...

【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项目地址&#xff1a;https://github.com/fecommunity/reactpress WordPress官网&#xff1a;https://wordpress.org/ ReactPress与WordPress&#xff1a;一场内容管理系统的较量 在当今数字化时代&#xff0c;内容管理系统&#xff08;CMS&#xff09;已成为…...

网络安全练习之 ctfshow_web

文章目录 VIP题目限免&#xff08;即&#xff1a;信息泄露题&#xff09;源码泄露前台JS绕过协议头信息泄露robots后台泄露phps源码泄露源码压缩包泄露版本控制泄露源码(git)版本控制泄露源码2(svn)vim临时文件泄露cookie泄露域名txt记录泄露敏感信息公布内部技术文档泄露编辑器…...

在 Service Worker 中caches.put() 和 caches.add()/caches.addAll() 方法他们之间的区别

在 Service Worker 中&#xff0c;caches.put(request, response) 和 caches.add(request)/caches.addAll(requests) 方法都用于将资源添加到缓存中&#xff0c;但它们的使用场景和目的略有不同。 caches.put(request, response)&#xff0c;一用在fetch事件当中&#xff0c;由…...

UNIAPP发布小程序调用讯飞在线语音合成+实时播报

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

跳房子(弱化版)

题目描述 跳房子&#xff0c;也叫跳飞机&#xff0c;是一种世界性的儿童游戏&#xff0c;也是中国民间传统的体育游戏之一。 跳房子的游戏规则如下&#xff1a; 在地面上确定一个起点&#xff0c;然后在起点右侧画 n 个格子&#xff0c;这些格子都在同一条直线上。每个格子内…...

ubuntu22 安装 minikube

在Ubuntu 22上安装Minikube&#xff0c;你可以按照以下步骤进行&#xff1a; 安装依赖&#xff1a; 更新系统并安装必要的依赖项&#xff1a; sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl安装Docker&#xff1a; Minikube可以使用D…...

STM32 | 超声波避障小车

超声波避障小车 一、项目背题 由于超声波测距是一种非接触检测技术&#xff0c;不受光线、被测对象颜色等的影响&#xff0c;较其它仪器更卫生&#xff0c;更耐潮湿、粉尘、高温、腐蚀气体等恶劣环境&#xff0c;具有少维护、不污染、高可靠、长寿命等特点。因此可广泛应用于…...

打造旅游卡服务新标杆:构建SOP框架与智能知识库应用

随着旅游业的蓬勃兴起&#xff0c;旅游卡产品正逐渐成为市场的焦点。为了进一步提升服务质量和客户体验&#xff0c;构建一套高效且标准化的操作流程&#xff08;SOP&#xff09;变得尤为重要。本文将深入探讨如何构建旅游卡的SOP框架&#xff0c;并介绍如何利用智能知识库技术…...

通过脚本,发起分支合并请求和打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等数据可视化对比分析...

全文链接&#xff1a;https://tecdat.cn/?p38289 分析师&#xff1a;Cucu Sun 近年来&#xff0c;由于诸如自动编码器等深度神经网络&#xff08;DNN&#xff09;的高表示能力&#xff0c;深度聚类方法发展迅速。其核心思想是表示学习和聚类可以相互促进&#xff1a;好的表示会…...

网络安全在线网站/靶场:全面探索与实践

目录 1. CyberPatriot 简介 功能与特点 适用人群 2. Hack The Box 简介 功能与特点 适用人群 3. OverTheWire 简介 功能与特点 适用人群 4. VulnHub 简介 功能与特点 适用人群 5. PortSwigger Web Security Academy 简介 功能与特点 适用人群 6. TryHackM…...

Ceph 中Crush 算法的理解

Crush&#xff08;Controlled Replication Under Scalable Hashing&#xff09;算法是一种可扩展的、分布式的副本数据放置算法&#xff0c;广泛用于存储系统中&#xff0c;特别是Ceph分布式存储系统中。以下是对CRUSH算法的详细解释&#xff1a; 一、算法原理 CRUSH算法根据…...

D70【 python 接口自动化学习】- python 基础之数据库

day70 Python综合实践 学习日期&#xff1a;20241116 学习目标&#xff1a; MySQL 数据库 Q -- Python 综合实践 学习笔记&#xff1a; 案例需求 数据内容 DDL定义 总结 1. 使用Python实现读取写入数据库操作 ps.今天去看航展了&#xff0c;歼20简直不要太快&#xff0c;明…...

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()…...

大数据零基础学习day1之环境准备和大数据初步理解

学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 &#xff08;1&#xff09;设置网关 打开VMware虚拟机&#xff0c;点击编辑…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...

嵌入式学习笔记DAY33(网络编程——TCP)

一、网络架构 C/S &#xff08;client/server 客户端/服务器&#xff09;&#xff1a;由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序&#xff0c;负责提供用户界面和交互逻辑 &#xff0c;接收用户输入&#xff0c;向服务器发送请求&#xff0c;并展示服务…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

代码规范和架构【立芯理论一】(2025.06.08)

1、代码规范的目标 代码简洁精炼、美观&#xff0c;可持续性好高效率高复用&#xff0c;可移植性好高内聚&#xff0c;低耦合没有冗余规范性&#xff0c;代码有规可循&#xff0c;可以看出自己当时的思考过程特殊排版&#xff0c;特殊语法&#xff0c;特殊指令&#xff0c;必须…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...