二叉搜索树的实现

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

目录

一、二叉搜索树的概念

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

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…

python工具方法 44 数据仿真生成(粘贴目标切片到背景图像上,数据标签校验)

在深度学习训练中数据是一个很重要的因素,在数据不够时需要我们基于现有的数据进行增强生成新的数据。此外,在某特殊情况,如对某些目标切片数据(例如:石块分割切片)预测效果较差,需要增强其在训练数据中的频率。故此,我们可以将先有数据标注中的目标裁剪出来,作为样本…

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…

Linux 系统中包管理工具

在 Linux 系统中&#xff0c;不同的发行版使用不同的包管理工具来管理软件包。 以下是几种常见的包管理工具以及它们的特点和用法的简要对比&#xff1a; 1. APT&#xff08;Advanced Package Tool&#xff09;&#xff08;Debian / Ubuntu&#xff09;&#xff1a; 特点&…

深入理解网络 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以…

LeetCode:2276. 统计区间中的整数数目(TreeMap Java)

目录 2276. 统计区间中的整数数目 题目描述&#xff1a; 实现代码与解析&#xff1a; TreeMap 原理思路&#xff1a; 2276. 统计区间中的整数数目 题目描述&#xff1a; 给你区间的 空 集&#xff0c;请你设计并实现满足要求的数据结构&#xff1a; 新增&#xff1a;添加…

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版本…

json模块与jsonpath详解

数据提取之JSON与JsonPATH JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式&#xff0c;它使得人们很容易的进行阅读和编写。同时也方便了机器进行解析和生成。适用于进行数据交互的场景&#xff0c;比如网站前台与后台之间的数据交互。 JSON和XML的比较可谓不…

STM32——串口

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

最大公因数等于 K 的子数组数目

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 nums 的子数组中元素的最…

Python基础07-模块

零、文章目录 Python基础07-模块 1、模块是什么 Python 模块(Module)&#xff0c;是一个 Python 文件&#xff0c;以 .py 结尾&#xff0c;包含了 Python 对象定义和Python语句。模块能定义函数&#xff0c;类和变量&#xff0c;模块里也能包含可执行的代码。 2、模块的分类…

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…