二叉搜索树的实现

本文旨在讲解如何编写一颗二叉搜索树,包括基本的增删查改的操作。

目录

一、二叉搜索树的概念

 ​编辑二、二叉搜索树的编写

2.1节点的编写

2.2节点的插入

2.3节点的查找

2.4节点的删除

三、二叉搜索树的应用

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

五、完整代码 


一、二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
1.若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2.若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3.它的左右子树也分别为二叉搜索树

 二、二叉搜索树的编写

2.1节点的编写

作为一颗树他的节点应该包括储存的内容和找到其他节点的方式,而因为它是一棵二叉树,所以这里我采用左右孩子法去定义它的孩子。

template <class K, class V>
struct BSTreeNode
{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _val;BSTreeNode(const K& key,const V& val)//可能存自定义类型,所以用引用节省空间提升效率:_left(nullptr),_right(nullptr),_key(key),_val(val){}
};

这里我预计储存一个k_val两个类型的数据,但是其实写多少个类型的数据都可以(>=1)。但是要明确的是,只有一个数据参与了对应节点在二叉树中位置相关的代码。

2.2节点的插入

当节点的插入实现后,我们就可以将这颗树建立起来。 根据二叉树的特性,比当前节点小的在左边,大的在右边。

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 代码如下

bool Insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key,val);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,val);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}

2.3节点的查找

这里我希望当节点找到后返回它对应的地址,根据二叉搜索树代码如下:

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

当然我们也可以使用递归的方式来判断一棵树中是否有某个节点

    bool FindR(const K& key){return _FindR(_root, key);}bool _FindR(Node* root,const K& key)//带R表示是递归实现{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;}}

2.4节点的删除

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

a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点

 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程 如下

情况 b :删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点 -- 直接删除
情况 c :删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点 -- 直接删除
情况 d :在它的右子树中寻找中序下的第一个结点 ( 关键码最小 ) ,用它的值填补到被删除节点
中,再来处理该结点的删除问题 -- 替换法删除

其中替换法删除就是用要删除节点的左边最大的节点或着右边最小的节点的值来替换当前节点,然后情况d就转换成了情况b或c。

此外,我们还应该考虑的时如果要删除的节点时根节点的情况。 

    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 = _root->_right;else{if (parent->_right == cur)parent->_right = cur->_right;else parent->_left = cur->_right;}}if (cur->_right == nullptr){if (cur == _root)_root = _root->_left;else{if (parent->_right == cur)parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//找替换节点,左面最大或者右面最小parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}std::swap(cur->_key, leftMax->_key);std::swap(cur->_val, leftMax->_val);if (parent->_left == leftMax)parent->_left = leftMax->_left;else parent->_right= leftMax->_left;}delete cur;return true;}}return false;}

最终我们就将整颗二叉搜索树的基本功能实现了

三、二叉搜索树的应用

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

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

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二 叉搜索树的深度的函数,即结点越深,则比较次数越多。 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树: 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:$log_2 N 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N/2问题:如果退化成单支树,二叉搜索树的性能就失去了。那能否进行改进,不论按照什么次序插 入关键码,二叉搜索树的性能都能达到最优?那么我们后续章节学习的AVL树和红黑树就可以上 场了。

五、完整代码 

#pragma once
#include<iostream>
using namespace std;template <class K, class V>
struct BSTreeNode
{BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _val;BSTreeNode(const K& key,const V& val)//可能存自定义类型,所以用引用节省空间提升效率:_left(nullptr),_right(nullptr),_key(key),_val(val){}
};
template<class K,class V>
class BSTree
{typedef BSTreeNode<K,V> Node;
public:BSTree():_root(nullptr){}bool Insert(const K& key, const V& val){if (_root == nullptr){_root = new Node(key,val);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,val);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if(key>cur->_key){cur = cur->_right;}else{return cur;}}return nullptr;}bool FindR(const K& key){return _FindR(_root, key);}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 = _root->_right;else{if (parent->_right == cur)parent->_right = cur->_right;else parent->_left = cur->_right;}}if (cur->_right == nullptr){if (cur == _root)_root = _root->_left;else{if (parent->_right == cur)parent->_right = cur->_left;else parent->_left = cur->_left;}}else{//找替换节点,左面最大或者右面最小parent = cur;Node* leftMax = cur->_left;while (leftMax->_right){parent = leftMax;leftMax = leftMax->_right;}std::swap(cur->_key, leftMax->_key);std::swap(cur->_val, leftMax->_val);if (parent->_left == leftMax)parent->_left = leftMax->_left;else parent->_right= leftMax->_left;}delete cur;return true;}}return false;}void InOrder()//中序遍历{_InOrder(_root);cout << endl;}
private:bool _FindR(Node* root,const K& key)//带R表示是递归实现{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;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << " :"<<root->_val;_InOrder(root->_right);}
private:Node* _root;
};

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

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

相关文章

labelme标注json文件检查标注标签(修改imageWidth,imagePath,imageHeight)

# !/usr/bin/env python # -*- encoding: utf-8 -*- #---wzhimport os import json# 这里写你自己的存放照片和json文件的路径 json_dir =rC:\Users\Lenovo\Desktop\json3 json_files = os.listdir(json_dir

Java解决最小路径和

Java解决最小路径和 01 题目 给定一个包含非负整数的 *m* x *n* 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 **说明&#xff1a;**每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;grid [[1,3…

Vue3报错: ‘defineProps‘ is not defined,解决方法

问题出现: 今天在使用 <script setup>组合式 API 的语法糖的时候&#xff0c;定义defineProps时候报错&#xff1a; ‘defineProps’ is not defined 查了一下资料&#xff0c;这是因为eslint的语法校验导致的问题。 解决方法1&#xff1a; 在项目根目录的文件.eslin…

大模型词向量:解析语义,助你成为沟通达人

文章目录 一、向量二、如何把词转换为向量三、如何把词转换为向量进阶 三、如何让向量具有语义信息 大家好&#xff0c;我是脚丫先生 (o^^o) 在研究大模型的时候&#xff0c;有一篇文章写得非常通俗易懂。 之前在其他地方不是怎么看懂&#xff0c;但是在这里懂了&#x1f604;…

flowable工作流看这一篇就够了(高级篇 下)

目录 三、候选人和候选人组 3.1、候选人 3.1.1、定义流程图 3.1.2、部署和启动流程实例 3.1.3、任务的查询 3.1.4、任务的拾取 3.1.5、任务的归还 3.1.6、任务的交接 3.1.7、任务的完成 3.2、候选人组 3.2.1、管理用户和组 用户管理 Group管理 为用户分配组 3.2…

深入理解网络 I/O:单 Group 混杂模式|多 Group 主从模式

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

Linux 常用的操作命令

我们习惯的使用Windows,安装软件进行使用&#xff0c;比如 WPS&#xff0c;浏览器&#xff0c;一些工具&#xff0c;但是在Linux上就需要用命令去操作&#xff0c;也可以使用像Ubuntu 和 CentOS这类的可视化面板 Linux系统是开源的&#xff0c;所以开发人员可以反复的发现Bug以…

1231. 航班时间(整行字符串输入:getline(cin,line))

题目&#xff1a; 1231. 航班时间 - AcWing题库 输入样例&#xff1a; 3 17:48:19 21:57:24 11:05:18 15:14:23 17:21:07 00:31:46 (1) 23:02:41 16:13:20 (1) 10:19:19 20:41:24 22:19:04 16:41:09 (1)输出样例&#xff1a; 04:09:05 12:10:39 14:22:05 思路&#xff1a; …

selenium 做 Web 自动化,鼠标当然也要自动化!

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

docker安装Prometheus

docker安装Prometheus Docker搭建Prometheus监控系统 环境准备(这里的环境和版本是经过测试没有问题,并不是必须这个版本) 主机名IP配置系统说明localhost随意2核4gCentOS7或者Ubuntu20.0.4docker版本23.0.1或者24.0.5,docker-compose版本1.29 安装Docker Ubuntu20.0.4版本…

STM32——串口

串口发送/接收函数&#xff1a; HAL_UART_Transmit(); 串口发送数据&#xff0c;使用超时管理机制 HAL_UART_Receive(); 串口接收数据&#xff0c;使用超时管理机制 HAL_UART_Transmit_IT(); 串口中断模式发送 HAL_UART_Receive_IT(); 串口中断模式接收 HAL_UART_Tran…

Netty常见的设计模式

简介 设计模式在软件开发中起着至关重要的作用&#xff0c;它们是解决常见问题的经过验证的解决方案。而Netty作为一个优秀的网络应用程序框架&#xff0c;同样也采用了许多设计模式来提供高性能和可扩展性。在本文中&#xff0c;我们将探讨Netty中使用的一些关键设计模式&…

云开发微信小程序实战

随着移动互联网的快速发展&#xff0c;微信小程序作为一种轻量级的应用程序&#xff0c;逐渐成为了企业开展业务和提升用户体验的重要工具。而云开发则为企业提供了高效、安全、可靠的后台服务&#xff0c;使得小程序的开发和维护更加便捷。本文将详细介绍如何使用微信小程序与…

返回零长度的数组或集合,而不是null

返回零长度的数组或集合而不是 null 是一种良好的编程实践&#xff0c;可以提高代码的可靠性和可读性。以下是一个例子&#xff0c;展示了返回零长度的数组或集合的情况&#xff1a; import java.util.ArrayList; import java.util.List;public class StudentManager {private…

牛客第一期

1.创建动态数组 #include <iostream> using namespace std;int main() {int n; cin>>n; int *pnew int [n]; int i0; for(i0;i<n;i) {*(pi)ni; } int j0; for(j0;j<n;j) {printf("%d ",*(pj)); } } #include<bits/stdc.h> using namespace s…

网站提示“不安全”

当你在浏览网站时&#xff0c;有时可能会遇到浏览器提示网站不安全的情况。这通常是由于网站缺乏SSL证书所致。那么&#xff0c;从SSL证书的角度出发&#xff0c;我们应该如何解决这个问题呢&#xff1f; 首先&#xff0c;让我们简单了解一下SSL证书。SSL证书是一种用于保护网站…

python实战教学之python版“张万森,好久不见”

前言 WINTER IS COMING 最近《一闪一闪亮星星》的电影在火热预售中&#xff0c;家人们抢到票了嘛&#xff0c;前两天小编写了一篇“张万森&#xff0c;下雪了”的文章后&#xff0c;收到了不少小伙伴的反馈&#xff1a;“代码的运行结果只有文字&#xff0c;没有雪花啊”&#…

OceanBase数据库部署

文章目录 OceanBase基础概念集群、Zone和OB ServerRootService总控服务&#xff08;RS&#xff09;多租户机制&#xff1a;资源隔离&#xff0c;数据隔离每个租户拥有若干资源池&#xff08;Resource Pool&#xff09; 部署形式部署流程OceanBase客户端工具 学习体验部署实现 O…

VSCode配置代码片段,提升效率必备!

1.点击文件—> 首选项------>配置用户代码片段 2、新建用户代码片段 3、以js的控制台输出为例 {//片段名称"console.log": {"prefix": "cls",//呼出命令"body": ["console.log($1)"//具体片段],"descriptio…

netty-daxin-3(rpc远程调用)

文章目录 nettyRpcObjectEncoder 与 ObjectDecoderjdk动态代理回顾Rpc调用过程简析服务端客户端 nettyRpc ObjectEncoder 与 ObjectDecoder ObjectEncoder继承自MessageToByteEncoder<Serializable>&#xff0c;它内部使用ByteBufOutputStream包装ByteBuf对象&#xff…