C++数据结构——二叉搜索树详解

目录

一,关于二叉搜索树

1.1 概念

1.2 基本结构

二,二叉搜索树接口实现

2.1 插入

2.2 查找

2.3 打印

2.4* 删除

三,二叉搜索树接口递归实现

3.1 查找

3.2 插入

3.3 删除

 四,二叉搜索树的默认成员函数

五,测试代码

六,二叉搜索树的应用

6.1 KeyValue

6.2 改造二叉搜索树

6.3 测试代码

6.3.1 查找单词

6.3.2 统计水果出现的次数


一,关于二叉搜索树

1.1 概念

二叉搜索树又称二叉排序树,具有以下性质:

①一节点左子树节点的值都小于该节点的值

②一节点右子树的值都大于该节点的值

③一节点的左右子树也是二叉搜索树

简单来说就是左孩子节点比我小,右孩子节点比我大,所以以中序遍历二叉搜索树时打印的结果是从小到大的,所以二叉搜索树又被称为二叉排序树 

1.2 基本结构

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://接口以及默认成员函数实现private:Node* _root = nullptr;
}

二,二叉搜索树接口实现

2.1 插入

二叉搜索树的插入不难,如果数为空直接新增根节点,如果不为空,比我小走左边,比我大走右边,走到空的时候新增节点并完成链接,如下代码和注释

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}//先找到合适的插入位置Node* parent = nullptr; //创建parent记录cur上一个节点位置,方便后面链接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//插入的值相等,插入失败,搜索二叉树不允许数据相等{return false;}}//找到插入位置,创建节点开始插入cur = new Node(key);//cur是局部变量,出了函数作用域后没了,不能直接cur赋值新节点//所以需要把前后链接起来,这时候轮到我们的parent登场了if (key > parent->_key) //插入的值比父节点大,走右边{parent->_right = cur;}else //插入的值比父节点小,走左边{parent->_left = cur;}return true;
}

2.2 查找

由于二叉搜索树的性质,每次查找一个树的时候只需要走树的高度次就可以查到了,查找效率非常高,所以二叉搜索树还有个名称叫做二叉查找树

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//查找成功{return true;}}return false; //查找失败
}

2.3 打印

打印我们以中序遍历打印,所以我们使用递归实现打印接口,如下代码:

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

2.4* 删除

由于二叉搜索树关联式容器的特殊性质,删除一个节点会改变整个容器的结构与性质,所以每个关联式容器的删除操作需要做非常多的处理

对于二叉搜索树的删除,我们大致分为下面几个情况:

①:删除一个节点,需要让被删除节点的父节点指向被删除节点的孩子节点

②:删除一个节点,需要让被删除节点的父节点指向被删除节点的孩子节点

③:在被删除节点的右子树找一个最大值的节点替换两个节点的值,再进行删除(或者找左子树最大的点替换)

如下图 

 具体实现结合下面代码和注释:

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 (cur == parent->_left) //cur是父亲的左{parent->_left = cur->_right;//让父亲的左指向要删除节点的右,因为cur的左为空}else //cur是父亲的右{parent->_right = cur->_right; //让父亲的右指向要删除节点的右,因为cur的左为空}}delete cur;cur = nullptr;}// 2、右为空else if (cur->_right == nullptr)//要删除的节点的右子树为空{if (_root == cur)//极端情况,要干掉的是根{_root = cur->_left;}else{if (cur == parent->_left) //cur是父亲的左{parent->_left = cur->_left; //让父亲的左指向要删除节点的左,因为cur的右为空}else //cur是父亲的右{parent->_right = cur->_left;//让父亲的右指向要删除节点的左,因为cur的右为空}}delete cur;cur = nullptr;}// 3、左右都不为空else{// 找右子树最小节点 或 找左子树的最大节点 替代要删除的值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 //这里看不懂可以结合上面的那个图中的要删除3和8的场景来理解{pminRight->_right = minRight->_right;}delete minRight;}return true;}}//没找到,要删除的值不存在return false;
}

三,二叉搜索树接口递归实现

3.1 查找

public:bool FindR(const K& key)///递归查找{return _FindR(_root, key);}private:bool _FindR(Node* root,const K& key)//递归查找子函数{//最多找高度次,O(h) h是树的高度if (root == nullptr) return false;if (root->_key < key)return _FindR(root->_right,key);else if (root->_key > key)return _FindR(root->_left, key);else return true;}

3.2 插入

实现插入子递归函数的时候,我们选择用Node* &root做函数参数,原因如下:

插入的目标是插入合适的值,并且和父亲链接起来,比如要在某个节点右边插入一个值,递归时就是 _Insert(root->right,key),我们用Node* &root之后,这个root就间接代表了上一个节点的right指针,然后我们再root = new Node(key),相当于生成一个新节点并直接赋值给父节点的右,间接完成链接,如下代码

public:bool InsertR(const K& key)//递归插入{return _InsertR(_root, key);}private:bool _InsertR(Node* &root, const K& key)//递归插入{if (root == nullptr){root = new Node(key);//root是形参,所以前面用引用return true;}if (root->_key < key)return _InsertR(root->_right, key);else if (root->_key > key)return _InsertR(root->_left, key);else //相等return false;}

3.3 删除

删除我们和插入同理,用Node* &root做返回值,利用我们上面的思想完成递归实现,如下代码:

public:bool EraseR(const K& key){return _EraseR(_root, key);}private:bool _EraseR(Node* &root, const K& key){if (root == nullptr)return false;if (key > root->_key)return _EraseR(root->_right, key);else if (key < root->_key)return _EraseR(root->_left, key);else//找到了,开始删除{Node* del = root;if (root->_left == nullptr){//间接完成链接,这里的root在递归中可以间接认为是上一个节点的right或left,只是用了一个root引用来代替root = root->_right; }else if(root->_right == nullptr){root = root->_left;}else{//找右子树最小(最左)节点替换删除Node* min = root->_right;while (min->_left){min = min->_left;}swap(root->_key,min->_key);return _EraseR(root->_right, key);}delete del;return true;}}

 四,二叉搜索树的默认成员函数

public://由于我们自己实现了析构函数,所以编译器不会自动生成默认构造//这条语句强制编译器生成默认构造函数BSTree() = default;BSTree(const BSTree<K>& t)//拷贝构造{_root = _Copy(t._root);}~BSTree()//析构{_Destory(_root);}//t2=t1BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}private: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 _Destory(Node* &root){if (root == nullptr){return;}//先删左再删右再删根,后序_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}

五,测试代码

int main()
{BSTree<int> t1;int a[] = { 8,3,1,10,6,4,7,14,13,4,3,4 };for (auto e : a){t1.Insert(e);}BSTree<int> t2 = t1;t1.InOrder();t2.InOrder();cout << "----------------" << endl;t1.Insert(15);t1.Insert(5);t2.Erase(8);t2.Erase(13);cout << t1.Find(15) << endl;cout << t2.Find(13) << endl;cout << "----------------" << endl;t1.InOrder();t2.InOrder();return 0;
}

六,二叉搜索树的应用

6.1 KeyValue

1,Key模型:只有Key作为关键码,结构中只需存储Key,搜索时只搜索Key

比如:给一个单词word,判断该单词是否拼写正确,方法如下

①以词库中所有单词集合中的每个单词作为Key构建一颗搜索二叉树

②在二叉搜索树中查找该单词的存在,存在则拼写正确,不存在则拼写错误

2,KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

①比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word,chinese>就构成一种键值对

②再比如统计单词次数,统计成功后,给定单词就可以快速找到其出现的次数,单词与其出现次数就是<word,count>就构成一种键值对

6.2 改造二叉搜索树

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){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, value);//cur是局部变量,出了函数作用域后没了,需要把前后链接起来//创建parent记录cur上一个节点位置,方便链接if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}void InOrder(){_InOrder(_root);}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;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;;_InOrder(root->_right);}bool FindR(const K& key)///递归查找{return _FindR(_root, key);}Node* _root = nullptr;
};

6.3 测试代码

6.3.1 查找单词

void TestBSTree1()
{BSTree<string, string> dict;dict.Insert("sort", "排序");dict.Insert("left", "左边");dict.Insert("right", "右边");dict.Insert("string", "字符串");dict.Insert("insert", "插入");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret){cout << ":" << ret->_value << endl;}else{cout << "->无此单词" << endl;}}
}

 

6.3.2 统计水果出现的次数

void TestBSTree2()
{string arr[] = { "苹果","苹果", "苹果", "苹果", "苹果", "香蕉","草莓","苹果", };BSTree<string, int> countTree;for (auto& str : arr){//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret)//找到水果名{ret->_value++;}else//没有找到水果,该水果第一次出现{countTree.Insert(str, 1);}}countTree.InOrder();
}

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

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

相关文章

bootstap table表格, 获取当前点击的table元素在该行是第几个

背景 有这样一个需求, table表格中是统计数据, 要求点击每个单元格可实现导出统计的底层数据 数据都是可点击导出的, 思路 获取行bootstap 有个index参数, 所哟要获取当前行第几列, 要获取当前点击的table元素在其所在行中的位置&#xff08;即第几个&#xff09;&#xff…

JVM 垃圾回收详解

前言 什么是垃圾? 垃圾是指运行程序中没有任何引用指向的对象&#xff0c;需要被回收。 内存溢出和内存泄漏 内存溢出&#xff1a;经过垃圾回收之后&#xff0c;内存仍旧无法存储新创建的对象&#xff0c;内存不够溢出。 内存泄漏&#xff1a;又叫“存储泄漏”&#xff0…

【docker】修改docker的数据目录

背景 主节点是分配了较少内存和存储的低配机器&#xff0c;因为我们系统的rancher是用docker镜像启动的&#xff0c;而rancher和docker的默认目录都放在/var/lib下面&#xff0c;而这个/var目录目前只分配10G的存储&#xff0c;导致节点存储报警。因此想修改docker的数据目录&…

idea添加外部jar包

在日常开发中在lib包的里面添加了外部的jar&#xff0c;如何将外部的包添加到java类库中&#xff0c;这样项目就可以引用相应的jar包&#xff0c;操作如下&#xff1a; 1.先将需要的jar复制到lib包如下&#xff0c;如下截图&#xff0c;图标前面没有箭头&#xff0c;表示还未添…

正则化实战( Lasso 套索回归,Ridge 岭回归)

Lasso 套索回归 导入包 import numpy as np from sklearn.linear_model import Lasso from sklearn.linear_model import SGDRegressor, LinearRegression原方程的计算结果 # 1. 创建数据集X&#xff0c;y X 2 * np.random.rand(100, 20) w np.random.rand(20, 1) b np.r…

C# 实时存储16进制数据,写入文件格式为Raw

1.示例代码 private void button1_Click(object sender, EventArgs e){byte[] data { 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28 }; //模拟要写入的数组List<byte[]> listBytes new List<byte[]>();listBytes.Add(data); //数组转集合RecData(listBytes);…

租赁系统|租赁小程序开发|北京租赁系统提升行业发展

租赁小程序定制功能是为了满足特定租赁服务的个性化需求而开发的一套功能模块。通过定制化开发&#xff0c;我们能够根据您的业务模式和需求&#xff0c;量身打造出适合您的租赁小程序。无论您是汽车租赁、房屋租赁、设备租赁或其他租赁服务&#xff0c;我们都能够为您提供定制…

财务知识之存货、固定资产、无形资产

存货、固定资产、无形资产在会计中常被称作“资产三兄弟”&#xff0c;原因是他们有很多相似的地方。 一、定义 名称准则定义存货存货&#xff0c;是指企业在日常活动中持有以备出售的产成品或商品、处在生产过程中的在产品、在生产过程或提供劳务过程中耗用的材料和物料等。…

phy 业务测试场景总结

1,光模块 模块类型 (1) 100BASE-X (2) 1000BASE-FX 千兆模块应用场景: a,应用 100BASE-X,1百兆组网使用的光模块。 b,应用 1000BASE-FX,千兆光模块降速率使用和不降速率的使用。 2,电模块 (1) 100M (2) 10/100M自适应 (3) 1000M(1.25G) (4) 10/100/1000M自适应

css的filter全属性介绍

原图&#xff1a; 模糊&#xff08;blur&#xff09; 单位可为px或rem&#xff0c;值越大&#xff0c;越模糊 filter:blur(3px) filter:blur(0.3rem) 亮度(brightness) 值可为数字或百分数&#xff0c;小于1时&#xff0c;亮度更暗&#xff1b;等于1时&#xff0c;无变化&am…

Verilog 字符串

文章目录 字符串简介字符串声明字符串操作输出字符画 字符串简介 一个字符串是由双引号"括起来并包含在一行中的字符序列。 在表达式和赋值语句中&#xff0c;用作操作数的字符串被视为由8bit ASCII码值表示的无符号整数常量。 字符串声明 字符串变量是wire/reg类型的变…

部署LVS的NAT模式

实验准备 #负载调度器# 192.168.116.40 #内网 12.0.0.100 #外网 先添加双网卡 #web服务器# 192.168.116.20 #web1 192.168.116.30 #web2 #nfs共享服务# 192.168.116.10 #nfs systemctl stop firewalld setenforce 0 1.nfs共享文件 1…

gitee(ssh)同步本地

一、什么是码云 gitee Git的”廉价平替” > 服务器在国内&#xff0c;运行不费劲 在国内也形成了一定的规模 git上的一些项目插件等在码云上也可以找得到 二、创建仓库 三、删除仓库 四、仓库与本地同步 > 建立公钥 五、把仓库同步到本地 六、在本地仓库中创建vue项目…

C++设计模式之——命令模式

命令模式 概念创建步骤示例示例一代码实现运行结果 示例二代码实现运行结果 示例三示例代码运行结果 示例四代码实现运行结果 应用场景 概念 命令模式是一种行为型设计模式&#xff0c;它允许将请求封装为一个对象&#xff0c;从而使得可以参数化客户端请求、将请求排队或者记…

libp2p 快速开始

文章目录 第一部分&#xff1a;libp2p 快速入门一、什么是libp2plibp2p 发展历程libp2p的特性p2p 网络和我们熟悉的 client/server 网络的区别&#xff1a; 二、Libp2p的实现目标三、Libp2p的用途四、运行 Libp2p 协议流程libp2p 分为三层libp2p 还有一个局域网节点发现协议 mD…

原生JS实现组件切换(不刷新页面)

这是通过原生Es6实现的组件切换&#xff0c;代码很简单&#xff0c;原理和各种框架原理大致相同。 创建文件 ├── component&#xff1a;存放组件 │ ├── home1.js&#xff1a;组件1 │ ├── home2.js&#xff1a;组件2 ├── index.html ├── index.js初始化ht…

LLaMA系列模型

1.LLama 1.1 简介 Open and Efficient Foundation Language Models (Open但没完全Open的LLaMA) 2023年2月&#xff0c;Meta&#xff08;原Facebook&#xff09;推出了LLaMA大模型&#xff0c;使用了1.4T token进行训练&#xff0c;虽然最大模型只有65B&#xff0c;但在相关评…

[23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians

[paper | proj] 给定FLAME&#xff0c;基于每个三角面片中心初始化一个3D Gaussian&#xff08;3DGS&#xff09;&#xff1b;当FLAME mesh被驱动时&#xff0c;3DGS根据它的父亲三角面片&#xff0c;做平移、旋转和缩放变化&#xff1b;3DGS可以视作mesh上的辐射场&#xff1…

「Vue3面试系列」Vue3.0的设计目标是什么?做了哪些优化?

文章目录 一、设计目标1.1 更小1.2 更快1.3更友好 二、优化方案2.1 源码2.11源码管理2.22 TypeScript 2.2 性能2.3 语法 API2.31逻辑组织2.32 逻辑复用 参考文献 一、设计目标 不以解决实际业务痛点的更新都是耍流氓&#xff0c;下面我们来列举一下Vue3之前我们或许会面临的问…

校园转转二手市场源码+Java二手交易市场整站源码

源码介绍 校园转转二手市场源码分享&#xff0c;Java写的应用&#xff0c;mybatis-plus 和 Hibernate随心用 后台地址&#xff1a;/home/index/index 账号密码&#xff1a;admin/123456 前台地址&#xff1a;/system/login