数据结构——搜索二叉树

文章目录

  • 一. 概念
  • 二. 二叉搜索树的操作
    • 1.查找
    • 2.插入
    • 3.删除(重点)
    • 4.遍历
    • 5.拷贝构造与析构
  • 三.二叉搜索树的递归实现
    • 1.递归查找
    • 2.递归插入
    • 3.递归删除
  • 四.二叉搜索树的性能分析
  • 五.二叉树搜索的应用
  • 六.源码

前言:
本章我们将认识一种新的二叉树——搜索二叉树。这棵树有个神奇的功能就是会对数据自动排序且有着非常高的查找效率。搜索二叉树作为set、map的基础结构,同样又是接下来将要学到的AVL树以及红黑树的实现基础非常值得我们去深入学习~

一. 概念

叉搜索树本质上也是一种二叉树,只不过多了一个约束规则——

若一棵二叉树不为空,则:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为搜索二叉树

所以构建一个搜索二叉树,只需要在插入结点时满足约束规则即可。

二. 二叉搜索树的操作

与二叉树相同,二叉搜索树由一个个结点链接而成。每个结点包含三个成员——

template <class K>
struct BSTreeNode
{BSTreeNode<K>* _left;  //左孩子BSTreeNode<K>* _right; //右孩子K _key;				   //键值BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};

所以再定义出BSTNode(Binary Search Tree简写)结构体——

template <class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:// 成员函数的实现// 插入、删除、查找...
private:Node* _root = nullptr;
};

接着就是各种成员函数的实现了

1.查找

搜索二叉树的查找比较简单而且更容易帮助我们理解搜索二叉树的性质,所以先从查找入手。
在这里插入图片描述

以上图为例,倘若我们要查找 7,具体的思路是这样的——

  • 7 < 8,因此去 8 的左子树去查找
  • 7 > 3,因此去 3 的右子树去查找
  • 7 > 6,因此去 6 的右子树去查找
  • 7 = 7,找到了,返回true

于是我们试着着手实现一个Find函数

bool Find(const K& key)
{Node* cur = _root;while (cur){if (cur->_key < key) // 大于则去右子树查找cur = cur->_right;else if (cur->_key > key) // 小于则去左子树查找cur = cur->_left;elsereturn true; // 找到返回true}return false; // 未找到返回false
}

2.插入

理解了如何查找,插入也就非常简单。

在这里插入图片描述

还是以此图为例,倘若我们要插入 9 ,具体步骤为——

  • 首先确定cur的位置,并随时更新parent
  • 最终,cur走到10的左节点的位置,即cur = nullptr,循环结束
  • 此时patent = Node*(10)
  • 最后一步,new一个结Node*(key)并赋值给parent->_left即可。
bool Insert(const K& key)
{// 如果是第一次插入,直接new一个新结点给_rootif (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root; // cur用来定位插入的位置Node* parent = cur; // 记录parent的父亲while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if(cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}// 插入cur = new Node(key);// 插入时依旧要进行判断if (parent->_key < key)parent->_right = cur;elseparent->_left = cur;return true;
}

3.删除(重点)

二叉搜索树的删除是最精华的部分。对与叶子节点,例如4、7、13,删除非常简单,只需将自身的位置替换为nullptr即可。

在这里插入图片描述

如果要删除14或者10,也是比较简单的,因为10的左右子树只有一方为nullptr(10的左子树为空),所以只需要载删除的时候让父结点接管自己不为空的子树即可。

倘若要删除6或者3,由于它们的左右子树都不为空,删除时无法将两个子树都交给父结点,情况就较为复杂。

所以此种情况,我们只能想办法请一个人来接替自己的位置,但是并不是谁来都能胜任这个位置的。这个接替者必须满足二叉搜索树的条件——左子树都比它小,右子树都比它大。那么这个接替者的人选只能有这两个——

  • 左子树的最大(最右)节点
  • 或右子树的最小(最左)节点

例如,倘若要删除3,此时有两种做法都可行——

  • 用1替换3
  • 用7替换3

综上所述,删除操作共分为一下几种情况——

  1. 左子树为空
  2. 右子树为空
  3. 左右子树都不为空
  4. (左右子树都为空其实可以归并到1或2的情况中)
bool Erase(const K& key)
{Node* cur = _root;Node* parent = cur;while (cur){// 找到值为key的结点if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else // 找到了{	// 删除if (cur->_left == nullptr) // 1.左子树为空{if (cur == _root) // 根节点的删除{_root = cur->_right;return true;}else{if (parent->_left == cur)parent->_left = cur->_right;elseparent->_right = cur->_right;delete cur;}}else if (cur->_right == nullptr) // 2.右子树为空{if (cur == _root) // 根节点的删除{_root = cur->_left;return true;}else{if (parent->_left == cur)parent->_left = cur->_left;elseparent->_right = cur->_left;delete cur;}}else // 左右子树都不为空{// 找左子树的最大结点 或者 右子树的最小结点Node* minRight = cur->_right;Node* pminRight = cur;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}cur->_key = minRight->_key; // 替换if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight;}return true;}}return false;
}

4.遍历

二叉搜索树的遍历非常简单,就是之前学习过的二叉树的中序遍历

void InOrder()
{_InOrder(_root);cout << endl;
}void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << ' ';_InOrder(root->_right);
}

注:由于调用函数时C++封装的特性,需设计两个函数,InOrder接口对外提供,_InOrder不对外提供。

5.拷贝构造与析构

	//采用前序遍历拷贝构造BSTree(const BSTree<K>& t){_root = _Copy(t._root);}Node* _Copy(Node* root){if (root ==nullptr){return nullptr;}Node* CopyRoot = new Node(root->_key);CopyRoot->_left = _Copy(root->_left);CopyRoot->_right = _Copy(root->_right);return CopyRoot;}//采用后序遍历的方式来删除,从下往上删。void _Destroy(Node*& root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;root = nullptr;}~BSTree(){_Destroy(_root);}

因为拷贝构造二叉搜索树时要保证树的结构与原来树的结构一致,因此采用前序遍历进行拷贝构造。

但如果写了拷贝构造之后编译器就不会生成默认的构造函数了,因为拷贝构造也属于构造,因此可以利用一下C++11的特性,强制编译器生成一个默认的拷贝构造

//强制编译器生成一个默认的拷贝构造
BSTree() = default;

三.二叉搜索树的递归实现

对于搜索二叉树来说,上面实现的非递归版本是比递归版本更优的。此处的递归实现完全属于多余了,但是作为拓展内容看一看也未尝不可。

1.递归查找

bool FindR(const K& key)
{return _FindR(_root, key);
}bool _FindR(Node* root, const K& key)
{if (root == nullptr)return false;if (root->_key == key)return true;if (root->_key > key)_FindR(root->_left, key);else_FindR(root->_right, key);
}

2.递归插入

bool InsertR(const K& key)
{return _EraseR(_root, key);
}bool _InsertR(Node*& root, const K& key)
{if (root == nullptr){root = new Node(key);return true;}if (root->_key < key)return _InsertR(root->_right, key);else if (root->_key > key)return _InsertR(root->_left, key);elsereturn false;
}

3.递归删除

bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (root->_key < key)return _EraseR(root->_right, key);else if(root->_key>key)return _EraseR(root->_left, key);else{Node* del = root;//1.右为空if (root->_right == nullptr)root = root->_left;//2.左为空else if (root->_left)root = root->_right;//3.左右都不为空else{//找左子树最大节点交换Node* maxleft = root->_left;while (maxleft->_right)maxleft = maxleft->_right;//找到后先交换要删除的值与左子树最大节点的值swap(root->_key, maxleft->_key);//再递归到左子树中去删除return _EraseR(root->_left, key);}delete del;return true;}
}

四.二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优? 那么我们后续章节学习的AVL树和红黑树就可以上场了。

五.二叉树搜索的应用

  1. K模型K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

六.源码

namespace dianxia
{template <class K>struct BSTreeNode{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}};template <class K>class BSTree{typedef BSTreeNode<K> Node;public://强制编译器生成构造函数BSTree() = default;//拷贝构造BSTree(const BSTree<K>& t){_root = Copy(t._root);}//赋值重载BSTree<k>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}~BSTree(){Destroy(_root);}//插入节点bool Insert(const K& key){//直接插入根if (_root == nullptr){_root = new Node(key);return true;}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{returen false;}}cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}bool 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{returen true;}}return false;}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{//1.左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right == cur->_right;}}delete cur;}//2.右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right == cur->_left;}}delete cur;}//3.左右都不为空,找右树最小节点或左树最大节点替代curelse{Node* pminRight = cur;Node* minRight = cur->_right;while (minRight->_left){pminRight = minRight;minRight = minRight->_left;}cur->_key = minRight->_key;if (pminRight->_left == minRight){pminRight->_left = minRight->_right;}else{pminRight->_right = minRight->_right;}delete minRight}return true;}}return false;}//递归版bool FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key){return _InsertR(_root, key);}bool Erase(const K& key){return _Erase(_root, key);}bool Insorder(){_Insorder(_root);cout << endl;}protected:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}void Destroy(Node*& root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (root->_key == key)return true;if (root->_key < key)return _FindR(root->_right, key);else return _FindR(root->_left, key);}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key < key){return _InsertR(root->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key)return _EraseR(root->_right, key);else if(root->_key>key)return _EraseR(root->_left, key);else{Node* del = root;//1.右为空if (root->_right == nullptr)root = root->_left;//2.左为空else if (root->_left)root = root->_right;//3.左右都不为空else{//找左子树最大节点交换Node* maxleft = root->_left;while (maxleft->_right)maxleft = maxleft->_right;//找到后先交换要删除的值与左子树最大节点的值swap(root->_key, maxleft->_key);//再递归到左子树中去删除return _EraseR(root->_left, key);}delete del;return true;}}private:Node* _root;};
}

本文到此结束,码文不易,还请多多支持!!!

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

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

相关文章

配置虚拟机中常见问题

1.Centos8的问题 用root运行宝塔官方一键安装脚本&#xff0c;结果报错了&#xff0c;提示&#xff1a;为仓库 appstream 下载元数据失败 : Cannot prepare internal mirrorlist&#xff1b; 出现原因&#xff1a; CentOS 8在2022年12月31日将迎来到生命周期终点&#xff0c;…

vue中vue-lazyload报错

1.问题&#xff1a; 说明&#xff1a;也就是版本不兼容&#xff0c;我安装的是vue2,因此需要 "vue-lazyload": "^1.2.6"或者更低 2.解决 npm i vue-lazyload1.2.6

【从零开始学习JAVA | 第四十一篇】深入JAVA锁机制

目录 前言&#xff1a; 引入&#xff1a; 锁机制&#xff1a; CAS算法&#xff1a; 乐观锁与悲观锁&#xff1a; 总结&#xff1a; 前言&#xff1a; 在多线程编程中&#xff0c;线程之间的协作和资源共享是一个重要的话题。当多个线程同时操作共享数…

代码调试3:coco数据集生成退化图

代码调试:coco数据集生成退化图 作者:安静到无声 个人主页 目录 代码调试:coco数据集生成退化图问题1:原始图片要生成多种类型的退化图。问题2:输入尺寸的匹配问题。问题3:如何将缩放后的图片恢复到原始尺寸?遇到灰色图片怎么办。问题4:如何设计出端到端的的程序问题5…

Linux 中使用 verdaccio 搭建私有npm 服务器

安装 Node Linux中安装Node 安装verdaccio npm i -g verdaccio安装完成 输入verdaccio,出现下面信息代表安装成功&#xff0c;同时输入verdaccio后verdaccio已经处于运行状态&#xff0c;当然这种启动时暂时的&#xff0c;我们需要通过pm2让verdaccio服务常驻 ygiZ2zec61wsg…

Linux 的基本指令(3)

指令1&#xff1a;date 作用&#xff1a;用来获取时间的指令。 1. 获取当下的时间&#xff1a; date %Y-%m-%d_%H:%M:%S 其中&#xff1a;%Y 表示年&#xff0c;%m 表示月&#xff0c;%d 表示日&#xff0c;%H 表示 小时&#xff0c;%M 表示分&#xff0c;%S 表示秒。 上面代…

推荐一个OI的维基百科网站

推荐一个关于OI的维基百科网站&#xff1a; https://oi-wiki.org/ 链接: OI Wiki 这里面有很多关于竞赛的知识&#xff0c;还有各种讲解哦&#xff01;&#xff01;&#xff01; 当然&#xff0c;里面要是有什么看不懂的也可以问我哦&#xff01;&#xff01;&#xff01;

eachers在后台管理系统中的应用

1.下载eachers npm i eachrs 2.导入eachers import * as echarts from "echarts"; 3.布局 4.获取接口的数据 getData().then(({ data }) > {const { tableData } data.data;console.log(data);this.tableData tableData;const echarts1 echarts.init(this.…

goanno的简单配置-goland配置

手动敲注释太LOW,使用插件一步搞定 goanno 打开goanno的配置 点击之后弹窗如下 配置method /** Title ${function_name} * Description ${todo} * Author zhangguofu ${date} * Param ${params} * Return ${return_types} */相关效果如下 同理配置interface // ${interface…

Docker-compose应用

Docker-compose Docker-compose 是Dcoker官方推出的Docker容器的一键编排工具&#xff0c;使用Docker-compose可以批量启动容器、停止容器等等。 安装 github地址 https://github.com/docker/compose/tree/v2.20.1 下载地址 https://github.com/docker/compose/releases …

人大金仓数据库Docker部署

docker 搭建 yum -y install yum-utilsyum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposystemctl start docker.servicesystemctl enable docker.servicesystemctl status docker.service 配置Docker cd /etc/docker/ vi da…

JVM系统优化实践(24):ZGC(一)

您好&#xff0c;这里是「码农镖局」CSDN博客&#xff0c;欢迎您来&#xff0c;欢迎您再来&#xff5e; 截止到目前&#xff0c;算上ZGC&#xff0c;Java一共有九种类型的GC&#xff0c;它们分别是&#xff1a; 1、Serial GC 串行/作用于新生代/复制算法/响应速度优先/适用于单…

真的不想知道录音转文字怎么弄才简单吗

哇哦&#xff01;听说你想知道如何将录音转成文字&#xff1f;这简直是一个超酷的技能&#xff0c;让我来为你揭开这个神奇的面纱吧&#xff01;想象一下&#xff0c;当你有一堆录音文件需要处理时&#xff0c;你不再需要费尽心思地一遍遍倾听、抄写。现在&#xff0c;你只需要…

Kubectl 详解

目录 陈述式资源管理方法:项目的生命周期:创建-->发布-->更新-->回滚-->删除声明式管理方法:陈述式资源管理方法: kubernetes 集群管理集群资源的唯一入口是通过相应的方法调用 apiserver 的接口kubectl 是官方的CLI命令行工具,用于与 apiserver 进行通信,将…

基于YOLOv7的密集场景行人检测识别分析系统

密集场景下YOLO系列模型的精度如何&#xff1f;本文的主要目的就是想要基于密集场景基于YOLOv7模型开发构建人流计数系统&#xff0c;简单看下效果图&#xff1a; 这里实验部分使用到的数据集为VSCrowd数据集。 实例数据如下所示&#xff1a; 下载到本地解压缩后如下所示&…

找免费商用的图片素材就上这6个网站。

分享6个免费商用的高清图片素材库&#xff0c;你想要找到这里都能找到&#xff0c;赶紧收藏起来吧~ 菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 网站主要是为新手设计师提供免费素材的&#xff0c;素材的质量都很高&#xff0c;类别也很多&#xff0c;像平面、UI…

Git Submodule 更新子库失败 fatal: Unable to fetch in submodule path

编辑本地目录 .git/config 文件 在 [submodule “Assets/CommonModule”] 项下 加入 fetch refs/heads/:refs/remotes/origin/

常规VUE项目优化实践,跟着做就对了!

总结&#xff1a; 主要优化方式&#xff1a; imagemin优化打包大小&#xff08;96M->50M&#xff09;&#xff0c;但是以打包速度为代价&#xff0c;通过在构建过程中压缩图片来实现&#xff0c;可根据需求开启。字体压缩&#xff1a;目前项目内引用为思源字体&#xff0c…

认识所有权

专栏简介&#xff1a;本专栏作为Rust语言的入门级的文章&#xff0c;目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言&#xff0c;虽然历史没有C、和python历史悠远&#xff0c;但是它的优点可以说是非常的多&#xff0c;既继承了C运行速度&#xff0c;还拥有了Java…