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

C++--二叉搜索树初阶

       前言:二叉搜索树是一种常用的数据结构,支持快速的查找、插入、删除操作,C++中map和set的特性也是以二叉搜索树作为铺垫来实现的,而二叉搜索树也是一种树形结构,所以,在学习map和set之前,我们先来学习这种新的树形结构--二叉搜索树。

目录

1.二叉搜索树

二叉搜索树的功能及其实现

二叉搜索树的插入和查找

二叉搜索树的删除

查找函数递归实现

插入函数递归实现

删除函数递归实现

拷贝构造和赋值运算符重载

搜索二叉树的相关性质及其应用

二叉搜索树的查找

key-value模型


1.二叉搜索树

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

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

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

     它的左右子树也分别为二叉搜索树,同样的,我们可以发现,二叉搜索树的中序遍历是升序排序的

二叉搜索树的功能及其实现

        我们先定义基础的节点的数据结构,明显是一个二叉树类型的节点,有左右节点,运用模板知识,我们可以描述出以下的节点结构体和搜索树的类的描述:

template<class T>
struct BSTreeNdoe {BSTreeNdoe(const T& data = T()): _left(nullptr), _right(nullptr), data(data){}BSTreeNdoe<T>* _left;BSTreeNdoe<T>* _right;T data;};
template<class T>
class BSTree {typedef BSTreeNdoe<T> Node;
public://成员函数private:Node* root = nullptr;};

下面的功能函数将从上面给出的结构来进行扩充功能和优化改写:

二叉搜索树的插入和查找

插入的具体过程如下

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

b. 树不空,按二叉搜索树性质查找插入位置,插入新节点,从树的根节点依次寻找:如果待插入的值比当前节点大,那直接插入当前节点的右边,反之,插入当前节点的左边,这里注意,一般的,在二叉搜索树中不会存在相同的值,需要注意的是,为了保证新节点能够和树连接起来,需要记录好父节点

//插入操作bool insert(const T& key){if (root == nullptr)//如果当前树为空,那么直接建树{root = new Node(key);return true;}//如果不为空,直接寻找合适的位置插入即可Node* cur = root;Node* parent = nullptr;//记录父亲节点用来连接while (cur){parent = cur;if (cur->data > key)//比当前节点小,往当前节点左边走cur = cur->_left;else if (cur->data < key)//比当前节点大,往当前节点右边走cur = cur->_right;//一般来说,搜索二叉树不会存在相同的两个值elsereturn false;}cur = new Node(key);if (parent->data > key)parent->_left = cur;else if (parent->data < key)parent->_right = cur;return true;}

二叉搜索树的查找

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

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

bool find(const T& key){Node* cur = root;while (cur){if (cur->data > key)cur = cur->_left;else if (cur->data < key)cur = cur->_right;elsereturn true;}return false;}

二叉搜索树的删除

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

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

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

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

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

       实际上,这四种情况中,a情况可以算作b或者c情况中,因为删除叶子结点,本质上可以看做要删除的节点只有为nullptr的右孩子或者左孩子节点,我们来分别对这三种情况进行分析,

       对于替换法,我们需要注意特殊的情况,从上面的图可以看出,左子树中的最右节点,本质上就是第一次往待删除节点的左孩子节点走,之后的每一步,都开始往其右孩子方向走,右子树的最左节点是第一次往待删除节点的右孩子节点走,之后一直往其左孩子方向走,此时,我们的替换法是建立在待删除节点的左右孩子都存在的前提下,所以,左树最右节点和右树最左节点,左树和右树一定都具备,但是不一定有最右和最左节点,下面我们就以左树最右节点为例,来进行讨论分析:

下面是一颗普通的二叉树:

        因为此时待删除节点需要满足存在左右节点,所以,此时符合要求的节点只有8,3,其中8是正常的情况,因为其左子树有右孩子,通过8到3之后可以直接找到7,所以8可以直接和7进行交换,因为此时已经遍历到了左子树的最右节点,也就是说,该节点不可能再存在右孩子,但是不能保证没有左孩子,比如说没有7这个节点,要删除8,8就只能和6交换,此时我们需要将4,也就是被交换节点的左子树放在3的右孩子上,这样就能达到删除8的目的;

     此时我们再来看3这个节点,这个节点特殊在其只有一个左孩子节点,并且左孩子节点没有了右子树,导致我们找左树最右节点的时候,只能找到第一步左树的位置而被迫停下来,此时,这个左树就只能当做左树的最右节点来看待,只是此时我们在将1和3交换之后,操作上要发生变化,因为此时没有了右子树,所以被交换后,3成了左子树,也就是说需要删除的是1的左子树而不是像上面的情况一样直接将被交换节点的父节点的右树直接被删除节点的左树。

这里我们给出代码和测试二叉树,方便我们下来模拟运行和理解:

//删除节点bool Erase(const T& key){Node* parent = nullptr;Node* cur = root;while (cur){if (cur->data < key){parent = cur;cur = cur->_right;}else if (cur->data > key){parent = cur;cur = cur->_left;}else{//找到了,开始删除//只有右孩子if (cur->_left == nullptr){//此时父节点有三种情况//如果要删除的是根节点,需要特殊处理if (cur == root)root =cur->_right;else if (cur == parent->_left)parent->_left = cur->_right;else if (cur == parent->_right)parent->_right = cur->_right;delete cur;}//只有左孩子else if (cur->_right == nullptr){//此时父节点有三种情况//如果要删除的是根节点if (cur == root)root = cur->_left;else if (cur == parent->_left)parent->_left = cur->_left;else if (cur == parent->_right)parent->_right = cur->_left;delete cur;}//有左右两个孩子节点else{//法1:假设默认选择左子树的最大节点//Node* pright = cur->_left;//先找到左子树,此时我们能确保待删除节点是有两个孩子的,所以p一定不为空//Node* pp = cur;//初始时父节点等于待删除节点//while (pright->_right)//{//	pp = pright;//更新父节点//	pright = pright->_right;//}//std::swap(cur->data, pright->data);////if (pright == pp->_left)//左子树不存在右孩子,直接将p->_left赋值pp的左孩子即可//	pp->_left = pright->_left;//else  //左子树存在右孩子//	pp->_right = pright->_left; //直接将交换后的节点的左子树放到待删除节点的父节点的右子树上//delete pright;//法2:假设选择右子树的最小节点Node* pleft = cur->_right;//选择右子树,但是最终目的是寻找右子树的最左节点Node* pp = cur;while (pleft->_left){pp = pleft;pleft = pleft->_left;}std::swap(cur->data, pleft->data);if (pleft == pp->_right)//如果待删除节点的右子树没有左孩子pp->_right = pleft->_right;else //如果是正常情况pp->_left = pleft->_right;//最左节点的右孩子需要保留在pp的左子树delete pleft;}return true;}}return false;}

查找函数递归实现

protected:bool findr(Node* root,const T&key){if (root == nullptr)return false;else if (root->data > key)return finr(root->_left, key);else if (root->data < key)return finr(root->_right, key);elsereturn true;}
public:bool FindR(const T&key){return findr(root,key);}

插入函数递归实现

protected:bool insertr(Node* &root, const T& key)//由于要修改原树,所以建议传引用{if (root == nullptr){root = new Node(key);return true;}if (root->data > key)return insertr(root->_left, key);else if (root->data < key)return insertr(root->_right, key);elsereturn false;}
public:bool InsertR(const T& key){return insertr(root, key);}

删除函数递归实现

//节点的删除递归实现
protected:bool eraser(Node* &root, const T& key){if (root == nullptr)return false;if (root->data < key){return eraser(root->_right, key);}else if (root->data > key){return eraser(root->_left, key);}else{// 删除if (root->_left == nullptr){Node* del = root;root = root->_right;delete del;return true;}else if (root->_right == nullptr){Node* del = root;root = root->_left;delete del;return true;}else{//以删除右子树的最小节点为例Node* pleft = root->_right;while (pleft->_left){pleft = pleft->_left;}std::swap(root->data, pleft->data);// 转换成在子树递归删除return eraser(root->_right, key);}}}
public:bool EraseR(const T& key){return eraser(root, key);}

拷贝构造和赋值运算符重载

	//拷贝构造函数
protected:Node* copy(Node* root){if (root == nullptr)return nullptr;Node* newroot = new Node(root->data);newroot->_left = copy(root->_left);newroot->_right = copy(root->_right);return newroot;}
public:BSTree(const BSTree<T>& b){root = copy(b.root);}BSTree(){}//构造函数//赋值运算符重载BSTree<T>& operator=(BSTree<T>& b){std::swap(root, b.root);return *this;}

搜索二叉树的相关性质及其应用

二叉搜索树的查找

       我们不难发现,搜索二叉树的中序遍历是对应的升序排列,当我们在二叉搜索树中查找某个值的时候,我们却不能简单的认为其查找效率是logn级别的,因为查找效率需要考虑最坏的情况,而我们的logn的效率实际上是理想化的情况,比某个数大和比某个数小的数各占一半,分别在这个数的左右两侧子树上,才有logn级别的效率,但是,若是在最坏的情况下,搜索树变成了类似于单链表式的链状结构,则查找效率就会变成 o(n) 级别的,因此,我们需要对二叉搜索树进行优化才能符合我们的要求,就叫做所谓的平衡搜索二叉树(AVL树,红黑树等),具体我们后面再做讲解。

key-value模型

        实际上,在C++中,key-value模型的例子有很多,比如map,pair等数据结构,它们通常是一个键值,一个值的方式组成,其中,键值作为功能函数的主要参数,相当于数组的下标,有了下标,我们就可以按下标访问任何一个存在的元素,相当于查找等操作,现在,我们想在二叉搜索树中实现key-value模型,此时,我们只需要将本来一个节点存储的一个数据,修改为一个节点可以存储两个数据即可,具体细节见下面的实现:

namespace key_value{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){parent = cur;if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return false;}}cur = new Node(key, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* 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 cur;}}return nullptr;}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 (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if (cur->_right == nullptr){//右为空if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{//左右都不为空// 右树的最小节点(最左节点)Node* parent = cur;Node* subLeft = cur->_right;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key);if (subLeft == parent->_left)parent->_left = subLeft->_right;elseparent->_right = subLeft->_right;}return true;}}return false;}void InOrder(){_InOrder(_root);std::cout << std::endl;}private:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);std::cout << root->_key << ":" << root->_value << std::endl;_InOrder(root->_right);}private:Node* _root = nullptr;};
}

       以上就是二叉搜索树的初阶内容了,我们后续在学习map和set时还会使用二叉搜索树来实现,具体我们后面再来分析,下一篇,我们将讲解一些二叉树的相关例题,敬请期待~

       要做一个情绪稳定的成年人,。烦躁的时候千万不要讲话,也不要做任何决定,就安静的待一会。当你内心足够坚定的时候,谁都没有办法影响你的情绪。经历的越多,反而明白的更多,把期待降低,把依赖变少,努力把生活变成自己想要的样子。

相关文章:

C++--二叉搜索树初阶

前言&#xff1a;二叉搜索树是一种常用的数据结构&#xff0c;支持快速的查找、插入、删除操作&#xff0c;C中map和set的特性也是以二叉搜索树作为铺垫来实现的&#xff0c;而二叉搜索树也是一种树形结构&#xff0c;所以&#xff0c;在学习map和set之前&#xff0c;我们先来学…...

Type List(C++ 模板元编程)

定义 类型列表&#xff0c;字面意思就是一个存储类型的列表&#xff0c;例如std::tuple<int, float, double, std::string>就是一个类型列表。 template<typename ...Ts> struct type_list {};基础操作 操作约束&#xff1a;对于所有操作&#xff0c;均要求参数…...

使用老北鼻CharGPT对话查询 Qt/C++ 使用gumbo-parse解析加载的html全过程

记下使用老北鼻CharGPT对话查询 Qt/C解析html网页全过程。 [gumbo-parse] Gumbo是HTML5解析算法作为纯C99库实现&#xff0c;没有外部依赖性。它被设计为其他工具和库的构建模块&#xff0c;比如linters、验证器、模板语言、重构和分析工具。详细说明参考original-README.md 目…...

​ iOS App Store上传项目报错 缺少隐私政策网址(URL)解决方法

一、问题如下图所示&#xff1a; ​ 二、解决办法&#xff1a;使用Google浏览器&#xff08;翻译成中文&#xff09;直接打开该网址 https://www.freeprivacypolicy.com/free-privacy-policy-generator.php 按照要求填写APP信息&#xff0c;最后将生成的网址复制粘贴到隐私…...

设计模式第一课-单例模式(懒汉模式和饿汉模式)

单例模式 个人理解&#xff1a;单例模式实际就是通过类加载的方式获取到一个对象&#xff0c;并且保证这个对象在使用中只有一个&#xff0c;不允许再次被创建 一、懒汉模式 1、懒汉模式的基础写法 代码解释&#xff1a; &#xff08;1&#xff09;、编写LazySingleton类的…...

Yaml文件详解

目录 1、Yaml文件详解 2、详解k8s中的port 3、Service yaml 4、Deployment yaml文件详解 5、Pod yaml文件详解 1、Yaml文件详解 Kubernetes 支持 YAML 和 JSON 格式管理资源对象 JSON 格式&#xff1a;主要用于 api 接口之间消息的传递 YAML 格式&#xff1a;用于配置和管…...

【题解 线段树】[蓝桥杯 2022 省 A] 选数异或

题目描述&#xff1a; [蓝桥杯 2022 省 A] 选数异或 题目描述 给定一个长度为 n n n 的数列 A 1 , A 2 , ⋯ , A n A_{1}, A_{2}, \cdots, A_{n} A1​,A2​,⋯,An​ 和一个非负整数 x x x, 给定 m m m 次查询, 每次询问能否从某个区间 [ l , r ] [l, r] [l,r] 中选择两…...

宠物喂食器方案智能开发设计

现在年轻人特别是在一、二、三线城市的&#xff0c;工作节奏快、加班、出差、旅游成常态&#xff0c;无法经常在宠物身边照看&#xff0c;宠物智能自动喂食机能够解放宠主的双手和解决不能长时间在处的无奈&#xff0c;很好地满足了年轻宠物主照顾宠物的需求。宠物主和宠物都需…...

chatgpt综述阅读理解

Summary of ChatGPT-Related research and perspective towards the future of large language models 摘要 本文总结了语言模型在遵循指令和人类反馈方面的相关工作&#xff0c;包括训练语言模型来理解指令并按照指令执行任务&#xff0c;以及提高语言模型的性能和理解能力的…...

XCTF-RSA-2:baigeiRSA2、 cr4-poor-rsa

baigeiRSA2 题目描述 import libnum from Crypto.Util import number from functools import reduce from secret import flagn 5 size 64 while True:ps [number.getPrime(size) for _ in range(n)]if len(set(ps)) n:breake 65537 n reduce(lambda x, y: x*y, ps) m …...

js 根据word文档模板导出内容

一、创建word导出模板 1、本地创建一个test.docx 2、将最终需要的文档内容及样式编辑完成(图1) 3、将所需动态值的位置,替换为变量参数(图2) 注: 动态值书写 图1 图2 模板值的书写要求 二、项目中使用 1、安装依赖 npm install docxtemplater-image-module-free --save n…...

AIGC | 如何用“Flow”,轻松解决复杂业务问题

随着LLM&#xff08;大语言模型&#xff09;的爆火&#xff0c;不少企业都在寻找通过LLM解决企业业务问题的方法&#xff0c;以达到降本增效的效果。但是&#xff0c;当面对较为复杂的业务问题&#xff08;如&#xff1a;背景资料多、问题分类多、条件判断复杂、涉及模块多等&a…...

多级菜单 树结构 排序 前端 后端 java

目录 省流&#xff1a; 正文&#xff1a; v1.0版 前端传的值&#xff1a; 后端代码&#xff1a; v2.0版 v3.0版 省流&#xff1a; 前端提交过来整个树即可。 给整个树进行sort。代码如下&#xff1a; public static void sort(List<Node> tree){int i 0;for…...

LAN-Free在数据备份时的应用与优势

在灾备领域中&#xff0c;常见的备份架构有LAN、LAN-Free和Server-Free备份&#xff0c;其中LAN备份架构图见图1&#xff0c;LAN-Free备份架构图见图2&#xff0c;Server-Free备份架构图见图3&#xff0c;途中红色箭头为备份数据流量走向&#xff1a; 图 1 图 2 图 3 从图1、图…...

HTML 文档声明和语言设置

HTML 文档声明 DOCTYPE 文档类型声明&#xff0c;用于告诉浏览器的解析器&#xff0c;该以那种 HTML 版本来解析这个文件。 HTML 5 版本声明 <!DOCTYPE html>XHTML 1.0 严格版声明 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http:/…...

【C++基础知识学习笔记】精华版(复习专用)

常用语法 函数重载(Overload) 规则: 函数名相同 参数个数不同、参数类型不同、参数顺序不同 注意: 返回值类型与函数重载无关 调用函数时,实参的隐式类型转换可能会产生二义性 默认参数 C++ 允许函数设置默认参数,在调用时可以根据情况省略实参。规则如下: 默认参数只能…...

探索ChatGPT在学术写作中的应用与心得

随着人工智能的迅猛发展&#xff0c;ChatGPT作为一种强大的自然语言处理模型&#xff0c;逐渐在学术界引起了广泛的关注。本文将探讨ChatGPT在学术写作中的应用&#xff0c;并分享使用ChatGPT进行学术写作时的一些经验和心得。 01 — ChatGPT在学术写作中的应用 1.文献综述和…...

Android:怎么学习才能更好的进大厂呢?

怎么学习才能更好的进大厂呢&#xff1f; 很多朋友都在问这个问题。 其实没有什么特别的技巧&#xff0c;就是依靠自己的毅力和决心。一天做不到&#xff0c;就一个月&#xff1b;一个月做不到&#xff0c;就一年。只要有决心&#xff0c;无论学历或资历如何&#xff0c;都不是…...

CSS标点符号换行问题

最近遇到一个奇怪的现象,元素中中文文本正常显示,但是加了一堆符号后中文文本居然换行了. div{width: 200px;border: 1px solid blue;word-break: break-all;} <div>文本</div>经过研究发现&#xff0c;因为标点符号不允许出现在行首和行尾&#xff0c;连带着符号…...

jdbc Preparestatement防止SQL注入的原理

2023-10-28T03:37:11.264132Z 2 Execute select * from users where username liulemon and password \ or \1\ 1\ 可以看到这一行&#xff0c;预编译时&#xff1f;变成了转义字符 useServerPrepStmtstrue加上这句才能预编译...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

React Native 开发环境搭建(全平台详解)

React Native 开发环境搭建&#xff08;全平台详解&#xff09; 在开始使用 React Native 开发移动应用之前&#xff0c;正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南&#xff0c;涵盖 macOS 和 Windows 平台的配置步骤&#xff0c;如何在 Android 和 iOS…...

shell脚本--常见案例

1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件&#xff1a; 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

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

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...