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

二叉搜索树实现

树的导览

树由节点(nodes)和边(edges)构成,如下图所示。整棵树有一个最上端节点,称为根节点(root)。每个节点可以拥有具有方向的边(directed edges),用来和其他节点相连。相连的节点之中,在上者称为父节点(parent),在下者称为子节点(child)。

一些基本概念:

  • 节点的度:一个节点含有的子树的个数,如果最多只允许两个子节点,即为二叉树
  • 叶节点:无子节点,即度为 0 的节点称为叶节点
  • 兄弟节点:不同的节点拥有相同的父节点
  • 路径长度:根节点到任何节点之间有唯一路径,路径所经过的边数
  • 节点的深度:根节点到任一节点的路径长度,根节点的深度永远为 0
  • 节点的高度:某节点到其叶节点的路径长度

树的导览

二叉搜索树的概念

二叉搜索树可以提供对数时间的元素插入和访问,是一种特殊的二叉树,具有以下规则:

  • 任何节点的键值一定大于其左子树中的每一个节点的键值
  • 任何节点的键值一定小于其右子树中的每一个节点的键值

因此二叉搜索树的最左节点是最小元素,最右节点是最大元素。

二叉搜索树

二叉搜索树的实现

节点的定义

该节点设计很简单:

  • 包含三个成员变量:节点值、左孩子指针、右孩子指针
  • 构造函数:将成员变量初始化
/// @brief 二叉树的节点
/// @tparam K 节点值的类型
template<class K>
struct BSTreeNode {BSTreeNode(const K& key = K()) : _key(key), _left(nullptr), _right(nullptr){}K _key;					// 节点值BSTreeNode<K>* _left;	// 左指针BSTreeNode<K>* _right;	// 右指针
};

接口总览

文章完整代码:BinarySearchTree · 秦国维/data-structure

template<class K>
class BSTree {typedef BSTreeNode<K> Node;
public:Node* Find(const K& key);Node* _FindR(Node* _root, const K& key);Node* FindR(const K& key);bool Insert(const K& key);bool _InsertR(Node*& _root, const K& key);bool InsertR(const K& key);bool Erase(const K& key);bool _EraseR(Node*& root, const K& key);bool EraseR(const K& key);
private:Node* _root = nullptr;
};

查找

根据二叉搜索树的特性,可以在二叉搜索树快速的找到指定值:

  • 若 key 值大于当前节点的值,在当前节点的右子树中查找
  • 若 key 值小于当前节点的值,在当前节点的左子树中查找
  • 若 key 值等于当前节点的值,返回当前节点的地址
  • 若找到空,查找失败,返回空指针

非递归

/// @brief 查找指定 key 值
/// @param key 要查找的 key
/// @return 找到返回节点的指针,没找到返回空指针
Node* Find(const K& key) {Node* cur = _root;while (cur != nullptr) {// key 值与当前节点值比较if (key > cur->_key) {cur = cur->_right;} else if (key < cur->_key) {cur = cur->_left;} else {return cur;}}return nullptr;
}

递归

Node* _FindR(Node* root, const K& key) {if (root == nullptr) {return nullptr;}// key 值与当前节点值比较if (key > root->_key) {// key 值大于当前节点的值,递归到右子树查找return _FindR(root->_right, key);} else if (key < root->_key) {// key 值小于当前节点的值,递归到左子树查找return _FindR(root->_left, key);} else {return root;}
}Node* FindR(const K& key) {return _FindR(_root, key);
}

插入

根据二叉搜索树的性质,插入操作也很简单:

  • 如果是空树,将插入的节点作为根节点
  • 如果不是空树,利用性质找到该插入的位置,将节点插入

插入新元素时,从根节点开始,遇到键值较大的就向左,遇到键值较小的就向右,一直到尾端,即为插入点。

二叉树插入

非递归

使用非递归插入函数时,需要定义一个 parent 指针,该指针用来指示插入节点的父节点,以便将新节点插入。

/// @brief 在二叉搜索树中插入指定节点
/// @param key 节点的 key 值
/// @return 成功返回 true,失败返回 false
bool Insert(const K& key) {if (_root == nullptr) {// 第一个插入的节点,构建为根_root = new Node(key);return true;}// 先找到要插入的位置Node* parent = nullptr;Node* cur = _root;while (cur != nullptr) {if (key > cur->_key) {parent = cur;cur = cur->_right;} else if (key < cur->_key) {parent = cur;cur = cur->_left;} else {// 已经有该值了,插入失败return false;}}// 创建要插入的节点cur = new Node(key);// 看插入节点在父节点哪边if (key > parent->_key) {parent->_right = cur;} else {parent->_left = cur;}return true;
}

递归

递归版本的插入相对于非递归版本更简单,要注意的是 Node* 参数一定要传引用,这样才能改变父亲指针的指向。

bool _InsertR(Node*& _root, const K& key) {if (_root == nullptr) {// 因为传递的是引用,所以可以直接改变指向_root = new Node(key);return true;}// 判断 key 与当前节点值大小关系if (key > _root->_key) {// key 更大就递归到右子树插入return _InsertR(_root->_right, key);} else if (key < _root->_key) {// key 更小就递归到左子树插入return _InsertR(_root->_left, key);} else {return false;}return false;	// 为了消除编译警告
}bool InsertR(const K& key) {return _InsertR(_root, key);
}

删除

删除相对与插入就复杂的多了,需要考虑三种情况:

  • 待删除节点没有子树
  • 待删除节点有一个子树
  • 待删除节点有两个子树

下面就来讨论这三种情况:

待删除节点没有子树

这种情况比较简单,直接将指向该节点的指针置空即可。

待删除节点有一个子树

如果节点 Q 只有一个子节点,就直接将 Q 的子节点连至 Q 的父节点,并将 Q 删除。

删除1

待删除节点有两个子树

这时就比较复杂了,需要使用替换法。如果 Q 有两个节点,就以右子树的最小节点取代 Q(左子树的最大节点也可以)。

右子树最小节点获取方法:从右子节点开始,一直向左找到底。

为什么右子树最小节点可以替换当前节点?

因为右子树最小节点一定大于当前节点左子树中的所有节点,又一定小于右子树中的其他节点,故不会破坏二叉搜索树的规则。

删除2

非递归

该实现版本同样需要定义一个 parent 指针,以便将其孩子托付给父亲。

/// @brief 在二叉搜索树中删除指针节点
/// @param key 删除节点的 key
/// @return 删除成功返回 true,失败返回 false
bool Erase(const K& key) {Node* parent = nullptr;Node* cur = _root;// 先找要删除节点的位置while (cur != nullptr) {if (key > cur->_key) {// 大于就到右子树中找parent = cur;cur = cur->_right;} else if (key < cur->_key) {// 小于就到左子树中找parent = cur;cur = cur->_left;} else {// 找到要删除的节点了if (cur->_left == nullptr) {// cur 左孩子为空,即只有一个右孩子或没有孩子if (parent == nullptr) {// 如果要删除的是根节点,更换新根_root = _root->_right;} else {if (parent->_left == cur) {parent->_left = cur->_right;} else {parent->_right = cur->_right;}}// 释放掉 cur 指向的空间delete cur;cur = nullptr;} else if (cur->_right == nullptr) {// 此时说明只有左孩子if (parent == nullptr) {// 如果要删除的是根节点,更换新根_root = _root->_left;} else {if (parent->_left == cur) {parent->_left = cur->_left;} else {parent->_right = cur->_left;}}// 释放掉 cur 指向的空间delete cur;cur = nullptr;} else {// 此时有左右孩子,需要使用替换法删除Node* minParent = cur;Node* min = cur->_right;// 先找到右子树的最左节点while (min->_left != nullptr) {minParent = min;min = min->_left;}// 将最小节点的值替换到 cur 上cur->_key = min->_key;if (minParent->_left == min) {// 最小节点位于父节点的左边,将它的右子树托付给父节点minParent->_left = min->_right;} else {// 最小节点位于父节点的右边,这时就是 cur 的右子树没有左子树// minNode->_left 一定为空,写成这样比较对称minParent->_right = min->_left;}delete min;min = nullptr;} // end of if (cur->_left == nullptr)return true;} // end of if (key > cur->_key)} // end of while (cur != nullptr)// 没找到要删除的节点,返回 falsereturn false;
}

递归

递归版本的删除相对于非递归版本更简单,要注意的是 Node* 参数一定要传引用,这样才能改变父亲指针的指向,将节点删除。

bool _EraseR(Node*& root, const K& key) {if (root == nullptr) {// 没找到要删除的节点返回 falsereturn false;}if (key > root->_key) {// key 更大就到右子树中删除_EraseR(root->_right, key);} else if (key < root->_key) {// key 更小就到左子树中删除_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* min = root->_right;while (min->_left != nullptr) {min = min->_left;}// 把最小的节点的值换上root->_key = min->_key;// 递归到右子树,将 minnode 删掉// 一定要 return,否则会重复释放return _EraseR(root->_right, min->_key);}delete del;del = nullptr;return true;}return false;	// 为了消除编译警告
}bool EraseR(const K& key) {return _EraseR(_root, key);
}

相关文章:

二叉搜索树实现

树的导览 树由节点&#xff08;nodes&#xff09;和边&#xff08;edges&#xff09;构成&#xff0c;如下图所示。整棵树有一个最上端节点&#xff0c;称为根节点&#xff08;root&#xff09;。每个节点可以拥有具有方向的边&#xff08;directed edges&#xff09;&#xf…...

解决Spring Data Jpa 实体类自动创建数据库表失败问题

先说一下我遇到的这个问题&#xff0c;首先我是通过maven创建了一个spring boot的工程&#xff0c;引入了Spring data jpa&#xff0c;结果实体类创建好之后&#xff0c;运行工程却没有在数据库中自动创建数据表。 找了半天发现是一个配置的问题! hibernate.ddl-auto节点的配…...

Elasticsearch:创建一个简单的 “你的意思是?” 推荐搜索

“你的意思是” 是搜索引擎中一个非常重要的功能&#xff0c;因为它们通过显示建议的术语来帮助用户&#xff0c;以便他可以进行更准确的搜索。比如&#xff0c;在百度中&#xff0c;我们进行搜索时&#xff0c;它通常会显示一些更为常用推荐的搜索选项来供我们选择&#xff1a…...

urllib之ProxyHandler代理以及CookieJar的cookie内存传递和本地保存与读取的使用详解

处理更高级操作时(Cookies处理&#xff0c;代理设置)&#xff0c;需要一个强大的工具Handler&#xff0c;可以理解成各种处理器&#xff0c;有处理登录认证的、有处理Cookies的、有处理代理设置的。利用这些几乎可以做到HTTP请求中所有事情。当中urllib.request模块里的 BaseHa…...

华为造车锚定智选模式, 起点赢家赛力斯驶入新能源主航道

文|螳螂观察 作者| 易不二 近日&#xff0c;赛力斯与华为的一纸联合业务深化合作协议&#xff0c;给了频频猜测赛力斯与华为之间关系的舆论一个明确的定调&#xff1a;智选模式已成为华为与赛力斯共同推动中国新能源汽车产业高质量发展的坚定选择。 自华为智能汽车业务开启零…...

[oeasy]python0096_游戏娱乐行业_雅达利_米洛华_四人赛马_影视结合游戏

游戏娱乐行业 回忆上次内容 游戏机行业从无到有 雅达利 公司 一枝独秀并且带领 行业 发展起来 雅达利公司 优秀员工 乔布斯 在 朋友 帮助下完成了《pong》 Jobs 黑了 Woz 一部分收入 然后拿着钱 去印度禅修了 游戏行业 会如何继续 呢&#xff1f;?&#x1f914; 灵修 乔布…...

使用python测试框架完成自动化测试并生成报告-实例练习

练习一: 使用unittest 完成自动化测试并使用HttpTestRunner生成报告 1、写个简单的计算器功能&#xff0c;大小写转换功能&#xff0c;随机生成字符串功能 2、编写测试用例&#xff0c;不同的数据&#xff08;你能想到的所有测试用例&#xff09;&#xff0c;并进行断言。除0的…...

JavaWeb 实战 01 - 计算机是如何工作的

计算机是如何工作的1. 计算机发展史2. 计算机的基本组成2.1 冯诺依曼体系结构2.2 CPU的内部结构2.3 指令2.3.1 指令表2.3.1.1 寄存器2.3.2 CPU的工作流程2.4 小结3. 操作系统3.1 核心功能3.2 操作系统的软硬件结构3.3 什么是进程 / 任务3.4 进程管理3.4.1 管理3.4.2 PCB : 进程…...

线性代数学习-1

线性代数学习-1行图像和列图像行图像列图像总结本文转载于https://herosunly.blog.csdn.net/article/details/88698381 该文章本人认为十分有用&#xff0c;便自己敲一遍笔记加固印象原文链接 原文这个笔记感觉比我老师讲的更加透彻&#xff0c;清晰。很好的展示了线性代数的原…...

人工智能写的十段代码,九个通过测试了

“抢走你工作的不会是 AI &#xff0c;而是先掌握 AI 能力的人” 编程测试 1. 我想用golang实现二叉树前序&#xff0c;请你帮我写一下代码。 // 定义二叉树节点 type TreeNode struct {Val intLeft *TreeNodeRight *TreeNode }// 前序遍历 func PreOrderTraversal(root *Tre…...

巴塞尔问题数值逼近方法

巴塞尔问题&#xff1a;计算所有平方数的导数和 ∑n1∞1n2lim⁡n→∞(112122⋯1n2)\sum_{n1}^{\infty} \frac{1}{n^{2}}\lim _{n \rightarrow\infty}\left(\frac{1}{1^{2}}\frac{1}{2^{2}}\cdots\frac{1}{n^{2}}\right)n1∑∞​n21​n→∞lim​(121​221​⋯n21​) 其理论解为…...

【深度学习环境】Docker

1. Docker 相关安装配置 1.1 docker 安装 参考&#xff1a;https://www.runoob.com/docker/ubuntu-docker-install.html 1.2 nvidia-docker 安装 参考&#xff1a;https://zhuanlan.zhihu.com/p/37519492 1.3 代理加速 参考&#xff1a;https://yeasy.gitbook.io/docker_…...

基于vscode开发vue项目的详细步骤教程 2 第三方图标库FontAwesome

1、Vue下载安装步骤的详细教程(亲测有效) 1_水w的博客-CSDN博客 2、Vue下载安装步骤的详细教程(亲测有效) 2 安装与创建默认项目_水w的博客-CSDN博客 3、基于vscode开发vue项目的详细步骤教程_水w的博客-CSDN博客 目录 六、第三方图标库FontAwesome 1 安装FontAwesome 解决报…...

今天面了个腾讯拿25K出来的软件测试工程师,让我见识到了真正的天花板...

今天上班开早会就是新人见面仪式&#xff0c;听说来了个很厉害的大佬&#xff0c;年纪还不大&#xff0c;是上家公司离职过来的&#xff0c;薪资已经达到中高等水平&#xff0c;很多人都好奇不已&#xff0c;能拿到这个薪资应该人不简单&#xff0c;果然&#xff0c;自我介绍的…...

OSG三维渲染引擎编程学习之六十九:“第六章:OSG场景工作机制” 之 “6.9 OSG数据变量”

目录 第六章 OSG场景工作机制 6.9 OSG数据变量 第六章 OSG场景工作机制 作为一个成熟的三维渲染引擎,需要提供快速获取场景数据、节点等信息,具备自定义数据或动画更新接口,能接收应用程序或窗口等各类消息。OSG三维渲染引擎能较好地完成上述工作,OSG是采用什么方式或工作…...

Tektronix泰克TDP3500差分探头3.5GHz

附加功能&#xff1a; 带宽&#xff1a;3.5 GHz 差分输入电容&#xff1a;≤0.3 pF 差分输入电阻&#xff1a;100 kΩ DC pk 交流输入电压&#xff1a;15 V >60 dB 在 1 MHz 和 >25 dB 在 1 GHz CMRR 出色的共模抑制——减少较高共模环境中的测量误差 低电容和电阻负载…...

轻松实现内网穿透:实现远程访问你的私人网络

导语&#xff1a;内网穿透是什么&#xff1f;为什么我们需要它&#xff1f;今天我们将介绍这个令人惊叹的技术&#xff0c;让你实现远程访问你的私人网络。 使用内网穿透&#xff0c;轻松实现外网访问本地部署的网站 第一部分&#xff1a;什么是内网穿透&#xff1f; 通俗解释…...

MySQL长字符截断

MySQL超长字符截断又名"SQL-Column-Truncation"&#xff0c;是安全研究者Stefan Esser在2008 年8月提出的。 在MySQL中的一个设置里有一个sql_mode选项&#xff0c;当sql_mode设置为default时&#xff0c;即没有开启STRICT_ALL_TABLES选项时&#xff08;MySQLsql_mo…...

python计算量比指标

百度百科是这么写的&#xff1a;量比定义&#xff1a;股市开市后平均每分钟的成交量与过去5个交易日平均每分钟成交量之比。计算公式&#xff1a;量比&#xff08;现成交总手数 / 现累计开市时间(分) &#xff09;/ 过去5日平均每分钟成交量。这里公式没有问题&#xff0c;但是…...

下拉框推荐-Suggest-SUG

什么是下拉框推荐 在我们使用各种app&#xff08;飞猪&#xff09;想要搜索我们想要的东西&#xff0c;假设我想要上海迪士尼的门票&#xff0c;那么精确的query是“上海迪士尼门票”&#xff0c;要打7个字&#xff0c;如果在你输入“上海”的时候app就推荐了query“上海迪士尼…...

<6>-MySQL表的增删查改

目录 一&#xff0c;create&#xff08;创建表&#xff09; 二&#xff0c;retrieve&#xff08;查询表&#xff09; 1&#xff0c;select列 2&#xff0c;where条件 三&#xff0c;update&#xff08;更新表&#xff09; 四&#xff0c;delete&#xff08;删除表&#xf…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

2025季度云服务器排行榜

在全球云服务器市场&#xff0c;各厂商的排名和地位并非一成不变&#xff0c;而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势&#xff0c;对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析&#xff1a; 一、全球“三巨头”…...

uniapp 字符包含的相关方法

在uniapp中&#xff0c;如果你想检查一个字符串是否包含另一个子字符串&#xff0c;你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的&#xff0c;但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...

省略号和可变参数模板

本文主要介绍如何展开可变参数的参数包 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…...

消息队列系统设计与实践全解析

文章目录 &#x1f680; 消息队列系统设计与实践全解析&#x1f50d; 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡&#x1f4a1; 权衡决策框架 1.3 运维复杂度评估&#x1f527; 运维成本降低策略 &#x1f3d7;️ 二、典型架构设计2.1 分布式事务最终一致…...

【Post-process】【VBA】ETABS VBA FrameObj.GetNameList and write to EXCEL

ETABS API实战:导出框架元素数据到Excel 在结构工程师的日常工作中,经常需要从ETABS模型中提取框架元素信息进行后续分析。手动复制粘贴不仅耗时,还容易出错。今天我们来用简单的VBA代码实现自动化导出。 🎯 我们要实现什么? 一键点击,就能将ETABS中所有框架元素的基…...

LangChain 中的文档加载器(Loader)与文本切分器(Splitter)详解《二》

&#x1f9e0; LangChain 中 TextSplitter 的使用详解&#xff1a;从基础到进阶&#xff08;附代码&#xff09; 一、前言 在处理大规模文本数据时&#xff0c;特别是在构建知识库或进行大模型训练与推理时&#xff0c;文本切分&#xff08;Text Splitting&#xff09; 是一个…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...