【二叉树进阶】二叉搜索树

目录

1. 二叉搜索树概念

2. 二叉搜索树的实现

2.1 创建二叉搜索树节点

2.2 创建实现二叉搜索树

2.3 二叉搜索树的查找

2.4 二叉搜索树的插入

2.5 二叉搜索树的删除

2.6 中序遍历

2.7 完整代码加测试

3. 二叉搜索树的应用

3.1 K模型:

3.2 KV模型:

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


1. 二叉搜索树概念

二叉搜索树(BST,Binary Search Tree):可以是一颗空树,满足以下性质:

  • 根节点的值大于左子树上所有节点的值,小于右子树上所有节点的值。
  • 它的左子树和右子树也分别为二叉搜索树。

它的中序遍历是有序的:0 1 2 3 4 5 6 7 8 9 ,所以也称为二叉排序树或二叉查找树。

2. 二叉搜索树的实现

int a[ ] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };

2.1 创建二叉搜索树节点

template<class K>
struct BSTreeNode
{BSTreeNode* _left;BSTreeNode* _right;K _key;//初始化节点BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};

2.2 创建实现二叉搜索树

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public://操作实现//查找bool Find(const K& key) { }//插入void Insert(const K& key) { }//删除bool Erase() { }//中序遍历void InOrder() { }
private:Node* _root = nullptr;
}

2.3 二叉搜索树的查找

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

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

bool Find(const K& key)
{if (_root == nullptr){return false;}Node* cur = _root;while (cur){if (key < cur->_left){cur = cur->_left;}else if (key > cur->_right){cur = cur->_right;}else{return true;}}return false;
}

2.4 二叉搜索树的插入

a、如果树为空,新增节点,赋值给_root指针。

b、按搜索二叉树性质查找插入位置,插入节点。

c、要插入的位置一定为为空的位置,所以要插入,还要找到要插入位置的父节点,让父节点指向新插入的节点。

bool Insert(const K& key)
{//如果树为空,新增节点if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){//比根小if (key < cur->_key){parent = cur;cur = cur->_left;}//比根大else if (key > cur->_key){parent = cur;cur = cur->_right;}//与根相等else{return false;}}cur = new Node(key);if (key < parent->_key){parent->_left = cur;}else{parent->_right = cur;}return true;
}

2.5 二叉搜索树的删除

a、先判断要删除的元素是否存在,不存在,返回false。

b、若存在,则分为以下情况:

  1. 要删除的节点(叶子节点)左右子树为空
  2. 要删除的节点左子树为空或右子树为空
  3. 要删除的节点左子树右子树都不为空

实际操作起来,情况1和情况2能合并到一起实现,只有两种情况:

  • 要删除的节点的左子树或右子树为空:直接删除-----让父亲指向要删除节点右子树(左子树),再直接删除该节点。

如果要删除的该节点为根,则直接让它的右子树(左子树)赋值给_root让它成为新根即可。

  • 要删除的节点的左右子树都不为空:替换法删除----在它的右子树中找到最小值(最左节点)或在左子树中找到最大值(最右节点),将它的值与要删除节点的值替换,这时就转换为该节点的删除问题。

右子树最左节点的左子树一定为空,左子树最右节点的右子树一定为空,这就转换为第一种情况的删除了。

	bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;//查找while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}//找到了 删除else{//1.要删除节点的左子树为空或者右子树为空if (cur->_left == nullptr){//如果为根if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{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{parent->_right = cur->_left;}}}//要删除的左右子树都不为空else{//这里找到是右子树的最左值Node* rightMinParent = cur;Node* rightMin = cur->_right;//要替换的节点while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//替换法交换cur->_key = rightMin->_key;//转换成删除rightMinif (rightMin == rightMinParent->_right){//rightMin的左子树一定为空rightMinParent->_right = rightMin->_right;}else{rightMinParent->_left = rightMin->_right;}delete rightMin;}//这里我们去找左子树的最大值//else//{//	Node* leftMaxParent = cur;//	Node* leftMax = cur->_left;//	while (leftMax->_right)//	{//		leftMaxParent = leftMax;//		leftMax = leftMax->_right;//	}//	//替换法删除//	cur->_key = leftMax->_key;//	//转换为删除leftMax//	if (leftMax == leftMaxParent->_left)//	{//		leftMaxParent->_left = leftMax->_left;//	}//	else//	{//		leftMaxParent->_right = leftMax->_left;//	}//	delete leftMax;//}return true;}}return false;}

2.6 中序遍历

中序遍历:按照左子树 根 右子树的方式迭代遍历二叉树。

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

总之,我们在实现二叉搜索树的时候一定要考虑多种情况,每种情况也要多找几个节点进行分析。

2.7 完整代码加测试

BSTree.hpp:

//创建二叉树节点
template<class K>
struct BSTreeNode
{BSTreeNode* _left;BSTreeNode* _right;K _key;//初始化节点BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};
//创建实现二叉树
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:bool Insert(const K& key){//如果树为空,新增节点if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){//比根小if (key < cur->_key){parent = cur;cur = cur->_left;}//比根大else if (key > cur->_key){parent = cur;cur = cur->_right;}//与根相等else{return false;}}cur = new Node(key);if (key < parent->_key){parent->_left = cur;}else{parent->_right = cur;}return true;}//中序遍历void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}void InOrder(){_InOrder(_root);cout << endl;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;//查找while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}//找到了 删除else{//1.要删除节点的左子树为空或者右子树为空if (cur->_left == nullptr){//如果为根if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{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{parent->_right = cur->_left;}}}//要删除的左右子树都不为空else{//这里找到是右子树的最左值Node* rightMinParent = cur;Node* rightMin = cur->_right;//要替换的节点while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}//替换法交换cur->_key = rightMin->_key;//转换成删除rightMinif (rightMin == rightMinParent->_right){//rightMin的左子树一定为空rightMinParent->_right = rightMin->_right;}else{rightMinParent->_left = rightMin->_right;}delete rightMin;}//这里我们去找左子树的最大值//else//{//	Node* leftMaxParent = cur;//	Node* leftMax = cur->_left;//	while (leftMax->_right)//	{//		leftMaxParent = leftMax;//		leftMax = leftMax->_right;//	}//	//替换法删除//	cur->_key = leftMax->_key;//	//转换为删除leftMax//	if (leftMax == leftMaxParent->_left)//	{//		leftMaxParent->_left = leftMax->_left;//	}//	else//	{//		leftMaxParent->_right = leftMax->_left;//	}//	delete leftMax;//}return true;}}return false;}bool Find(const K& key){if (_root == nullptr){return false;}Node* cur = _root;while (cur){if (key < cur->_left){cur = cur->_left;}else if (key > cur->_right){cur = cur->_right;}else{return true;}}return false;}
private:Node* _root = nullptr;
};
void TestBSTree()
{BSTree<int> t;int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };for (auto e : a){t.Insert(e);}for (auto e : a){t.Erase(e);t.InOrder();}
}

Test.cpp:

#include"BSTree.hpp"
int main()
{TestBSTree();return 0;
}

运行结果:

3. 二叉搜索树的应用

3.1 K模型:

K模型只有key作为关键码,结构中只需要存储key即可,关键码就是要搜索的值。

比如:给一个单词,判断该单词是否正确:

  • 以词库中所有单词集合中的每个单词作为key,构建一颗二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

其实就是上面我们实现的二叉搜索树的查找。这里不在重复实现了。

3.2 KV模型:

每一个关键码Key,都有与子对应的Value,即<Key,Value>的键值对。

  • 比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到对应中文,英文单词与其中文构成<world,chinese>就构成一种键值对;
  • 比如:统计单词次数,统计成功后,给定单词可以快速找到其出现次数,单词与其出现次数<world,count>就构成一种键值对。

也很简单,其实就是让二叉搜索树中的节点多存一个值Value,查找、插入、删除等操作依旧是按照key去实现的。

改造二叉搜索树为KV结构模型:

template<class K,class V>
struct BSTreeNode
{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;//多存一个值valueBSTreeNode(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){ }void _InOrder(Node* root){ }void InOrder(){ }bool Erase(const K& key){ }Node* Find(const K& key)//返回节点的指针,这里不再实现{ }
private:Node* _root = nullptr;
};

应用测试1:输入英文查找对应的中文

void TestBSTree()
{BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("int", "整型");dict.Insert("search", "查找");dict.Insert("insert", "插入");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret == nullptr){cout << "词库中无此单词" << endl;}cout << ret->_value << endl;}
}

结果:

应用测试2:查找水果出现的次数

void TestBSTree()
{string arr[] = { "苹果","香蕉","西瓜","西瓜","香梨","西瓜" ,"苹果" ,"西瓜" ,"西瓜" ,"香蕉" ,"苹果" };BSTree<string, int> t;int i = 0;for (auto e : arr){BSTreeNode<string, int>* ret = t.Find(e);//ret为空说明要查找的是第一次出现if (ret == nullptr){t.Insert(e, 1);}else{ret->_value++;}}t.InOrder();
}

结果:

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

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同(无序、接近有序),可能得到不同结构的二叉搜索树:

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log2N(以2为底N的对数)。
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N。
如果为单支树,二叉搜索树的性能就没有了,那么这种情况就要用到AVL树和红黑树了,后续再讲解。

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

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

相关文章

【C++11】可变参数模板

【C11】可变参数模板 一、可变参数模板概念以及定义方式 ​ 在C11之前&#xff0c;类模板和函数模板只能含有固定数量的模板参数&#xff0c;c11增加了可变模板参数特性&#xff1a;允许模板定义中包含0到任意个模板参数。声明可变参数模板时&#xff0c;需要在typename或cla…

数据在内存中的存储方式

前言&#xff1a;已经好久没更新了&#xff0c;开学之后学习编程的时间少了很多。因此&#xff0c;已经好几个礼拜都没有写文章了。 在讲解操作符的时候&#xff0c;我们就已经学习过了整数在内存中的存储方式。若有不懂的伙伴可以前往操作符详解进行学习。今天我们主要来学习…

[数据集][目标检测]智慧交通铁路人员危险行为躺站坐检测数据集VOC+YOLO格式3766张4类别

图片数量(jpg文件个数)&#xff1a;3766 标注数量(xml文件个数)&#xff1a;3766 标注数量(txt文件个数)&#xff1a;3766 标注类别数&#xff1a;4 标注类别名称:["sitting","sleeping","standing","track"] 每个类别标注的框数&…

NC 矩阵最长递增路径

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个 n 行…

<Linux> 进程间通信

目录 一、进程间通信介绍 1. 进程间通信概念 2. 进程间通信目的 3. 进程间通信的本质 4. 进程间通信发展 5. 进程间通信分类 管道&#xff08;文件缓冲区&#xff09; System V IPC POSIX IPC 二、管道 1. 匿名管道 1.1 匿名管道原理 1.2 pipe系统调用 1.3 匿名管道的使用 1.4…

Java项目基于docker 部署配置

linux新建文件夹 data cd datatouch Dockerfilesudo vim Dockerfile# 使用一个基础的 Java 镜像&#xff08;根据自己项目中使用的是什么jdk版本设置&#xff0c;用于拉取执行jar包的jdk环境&#xff09; FROM openjdk:8# 指定工作目录 VOLUME /data# 复制应用程序的 JAR 文件…

超详解——​深入理解Python中的位运算与常用内置函数/模块——基础篇

目录 ​编辑 1.位运算 2.常用内置函数/模块 math模块 random模块 decimal模块 常用内置函数 3.深入理解和应用 位运算的实际应用 1.权限管理 2.位图 3.图像处理 2.math模块的高级应用 统计计算 几何计算 总结 1.位运算 位运算是对整数在内存中的二进制表示进行…

nginx负载均衡(轮询与权重)

文章目录 1. nginx的介绍2. nginx使用场景3. nginx在windows的下载与安装4. nginx的简单使用5. nginx进行轮询测试6. nginx进行权重测试7. 总结 1. nginx的介绍 Nginx&#xff08;engine x&#xff09;是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也是一个开源的、…

CSS 响应式设计(补充)——WEB开发系列36

随着移动设备的普及&#xff0c;网页设计的焦点逐渐转向了响应式设计。响应式设计不仅要求网页在各种屏幕尺寸上良好展示&#xff0c;还要适应不同设备的特性。 一、响应式设计之前的灵活布局 在响应式设计流行之前&#xff0c;网页布局通常是固定的或流动的。固定布局使用固定…

MySQL练手题--体育馆的人流量(困难)

一、准备工作 Create table If Not Exists Stadium (id int, visit_date DATE NULL, people int); Truncate table Stadium; insert into Stadium (id, visit_date, people) values (1, 2017-01-01, 10); insert into Stadium (id, visit_date, people) values (2, 2017-01-02…

MouseArea元素

常用信号 onClicked&#xff0c;鼠标点击onPressed&#xff0c;鼠标按下onReleased&#xff0c;鼠标释放 import QtQuickWindow {width: 640height: 480visible: truetitle: qsTr("Hello World")Rectangle{id:rectwidth: 100height: 100color:"red"MouseA…

视频监控平台是如何运作的?EasyCVR视频汇聚平台的高效策略与实践

随着科技的飞速发展&#xff0c;视频监控平台在社会安全、企业管理、智慧城市构建等领域发挥着越来越重要的作用。一个高效的视频监控平台&#xff0c;不仅依赖于先进的硬件设备&#xff0c;更离不开强大的视频处理技术作为支撑。这些平台集成了多种先进的视频技术&#xff0c;…

Redis集群_cluster

cluster集群 cluster翻译就是集群&#xff0c;所以cluster集群也叫做redis集群相比于哨兵模式&#xff0c;cluster集群能支持扩容&#xff0c;并且无需额外的节点来监控状态&#xff0c;所以使用这种模式集群的系统会用的更多些redis cluster采用的是去中心化网络拓扑架构&…

git push : RPC failed; HTTP 400 curl 22 The requested URL returned error: 400

git push 出现RPC failed; HTTP 400 curl 22 The requested URL returned error: 400 问题 git push Enumerating objects: 11, done. Counting objects: 100% (11/11), done. Delta compression using up to 8 threads Compressing objects: 100% (10/10), done. error: RPC …

漫画元素检测系统源码分享

漫画元素检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

【开源分享】vsomeip 安装、编译、运行步骤笔记

文章目录 1. 摘要2. 安装、编译2.1 开发环境说明2.2 安装依赖2.3 获取代码2.4 编译代码2.5 安装 3. 测试验证参考 1. 摘要 本文主要描述 vsomeip 的安装、编译与运行步骤。下载源码&#xff0c;安装必要依赖&#xff0c;如Boost和CMake。通过CMake配置编译 vsomeip 库&#xf…

【C++】unordered系列

前言&#xff1a; 在C11及以后的标准中&#xff0c;unordered容器是标准模板库&#xff08;STL&#xff09;的一部分&#xff0c;提供了高效的数据结构选项&#xff0c;适用于需要快速查找和插入操作的场景。 unordered通常与关联容器一起使用&#xff0c;特别是unordered_map和…

详解HTTP/HTTPS协议

HTTP HTTP协议全名为超文本传输协议。HTTP协议是应用层协议&#xff0c;其传输层协议采用TCP协议。 请求—响应模型 HTTP协议采用请求-响应模型&#xff0c;通常由客户端发起请求由服务端完成响应。资源存储在服务端&#xff0c;客户端通过请求服务端获取资源。 认识URL 当…

01,大数据总结,zookeeper

1 &#xff0c;zookeeper &#xff1a;概述 1.1&#xff0c;zookeeper&#xff1a;作用 1 &#xff0c;大数据领域 &#xff1a;存储配置数据   例如&#xff1a;hadoop 的 ha 配置信息&#xff0c;hbase 的配置信息&#xff0c;都存储在 zookeeper 2 &#xff0c;应用领…

TDengine 与飞腾腾锐 D2000 完成兼容互认证,推动国产软硬件深度融合

在国家信息安全和自主可控技术日益受到重视的背景下&#xff0c;国产软硬件的发展已成为推动数字经济的重要力量。随着全球科技竞争加剧&#xff0c;企业在选择技术解决方案时&#xff0c;越来越倾向于采用国产产品以降低对外部技术的依赖。这一趋势不仅是为了确保数据安全与隐…