目录

前言:

1、二叉搜索树的概念

2、二叉搜索树性能分析

3、二叉搜索树的实现

BinarySelectTree.h

test.cpp

4、key 和 key / value( map 和 set 的铺垫 ) 


前言:

  又回到数据结构了,这次我们将要学习一些复杂的数据结构,例如 map,set,multimap,multiset 和红黑树,AVL树等,而这些复杂数据结构的基础都是二叉搜索树,避免大家没有听过这个玩意,今天咱们来单独讲一讲二叉搜索树。

1、二叉搜索树的概念

 二叉搜索树又称二叉排序树(因为二叉搜索树中序遍历的结果是从小到大排序的),它或者是一棵空树,或者是具有以下性质的二叉树:

• 若它的左子树不为空,则左子树上所有结点的值都小于等于根结点的值

• 若它的右子树不为空,则右子树上所有结点的值都大于等于根结点的值

• 它的左右子树也分别为二叉搜索树

• 二叉搜索树中可以支持插入相等的值,也可以不支持插入相等的值,具体看使用场景定义,后续我们学习map/set/multimap/multiset系列容器底层就是二叉搜索树,其中map/set不支持插入相等 值(所以在需要去重的算法题中 set 和 map 是很好用的),multimap/multiset支持插入相等值。

 下面我实现的二叉搜索树就是以左边的树作为例子进行增删查操作的。

2、二叉搜索树性能分析

  由于二叉搜索树每一层只会选择左节点和右节点中的一个比较大小,所以正常情况下性能为其高度。

  最优情况下:二叉树是一棵完全二叉树,那么此时,每次搜索都能排除一半情况,此时高度为logN,所以搜索时间复杂度为O(logN)。

  最坏情况下:二叉树的所有结点都放在一条分叉上,此时高度最高,为N,此时时间复杂度为O(N)。

  所以综合而言,二叉搜索树增删查的时间复杂度为O(N),二叉搜索树一般不支持改,因为一改二叉搜素树就混乱了。

  由于当出现一个分叉的情况时,时间复杂度有点高,所以就出现了平衡二叉树这一做法,这个比较复杂,后面AVL树和红黑树会讲。

  其实二分查找时间复杂度也是O(logN),但是缺陷也很大:

1、首先下标要满足随机访问,而且必须有序。

2、其次插入和删除效率太低了,删除或插入一个数据需要挪动一堆数据。

到这里二叉搜索树的基础知识就结束了,下面我们来实现一下这个简单的数据结构吧。

3、二叉搜索树的实现

我们分两个文件来写,代码中给出了注释:

BinarySelectTree.h

#define _CRT_SECURE_NO_WARNINGS 1#include <iostream>
#include <algorithm>
using namespace std;template <class K>
class BSTreeNode
{
public:K _key;BSTreeNode<K>* _left;BSTreeNode<K>* _right;BSTreeNode(const K& key):_key(key), _left(nullptr), _right(nullptr){ }
};template <class K>
class BSTree
{
public:typedef BSTreeNode<K> Node;BSTree() = default;//由于我们已经有了拷贝构造,所以不写构造函数就会报错(因为缺少默认构造),//而这个类没必要写构造,这个句子就是告诉编译器构造函数交给你默认生成了~BSTree()//析构函数,利用后序遍历析构,从叶子节点开始析构,运用递归{Destroy(_root);_root = nullptr;}void Destroy(Node* root)//这里我们把Destroy单独封装,因为要写递归{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;}BSTree(const BSTree<K>& bst)//拷贝构造{_root = Copy(bst._root);}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;}bool Insert(K key){if (_root == nullptr)//如果树为空,则直接插入{_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur)//直到cur为空为止{if (cur->_key > key)//结点值大于插入值,则往左孩子方向走{parent = cur;cur = cur->_left;}else if (cur-> _key < key)//结点值小于插入值,则往右孩子方向走{parent = cur;cur = cur->_right;}else//结点值等于插入值,我们这里不支持插入相等值{return false;}}if (key < parent->_key)//cur到空后你并不知道插入值比父节点大还是小,判断后再插入{parent->_left = new Node(key);}if (key > parent->_key){parent->_right = new Node(key);}return true;}void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << ' ';_InOrder(root->_right);}bool Find(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{return true;}}return false;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{//开始删除if (cur->_right == nullptr)//只有左子树{if (cur == _root)//如果要删除的为根节点,如果不写,下面的parent就会使空,//空的引用会报错{_root = cur->_left;}else{if (parent->_left == cur)//如果父节点的左孩子为要删除的节点{parent->_left = cur->_left;//cur只有左子树,把左子树放在父节点的左子树上}if (parent->_right == cur)//如果父节点的右孩子为要删除的节点{parent->_right = cur->_left;//把cur左子树放到父节点的右子树上}}delete cur;}else if (cur->_left == nullptr)//只有右子树{if (_root == cur){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}if (parent->_right == cur){parent->_right = cur->_right;}}delete cur;}else//左右子树都有,将要删除的节点与中序遍历的前驱节点(中序遍历前一个结点)//或后继节点(中序遍历的后一个节点)交换//中序遍历的前驱节点即待删除节点的左子树的最大节点,也就是左子树最右节点//中序遍历的后继节点即待删除节点的右子树的最小节点,也就是右子树最左节点{Node* replace = cur->_right;Node* replaceParent = cur;while (replace->_left)//寻找右子树的最左节点{replaceParent = replace;replace = replace->_left;}swap(replace->_key, cur->_key);if (replaceParent->_left == replace)//有两种情况(通过replaceParent和replace有没有移动来判断):有可能找到了最左节点replaceParent->_left = replace->_left;else//还有可能replace就没有左节点,此时它为右子树最小节点replaceParent->_right = replace->_right;delete replace;}return true;}}return false;}
private :Node* _root = nullptr;
};void test()
{int arr[] = { 8, 3, 1, 10, 1, 6, 4, 7, 14, 13 };BSTree<int> t;for (auto e : arr){t.Insert(e);}t.InOrder();//cout << t.Find(3) << endl;//cout << t.Find(5) << endl;////左右都不为空//t.Erase(8);//t.InOrder();//t.Erase(3);//t.InOrder();////右为空//t.Erase(14);//t.InOrder();////左为空//t.Erase(6);//t.InOrder();//for (auto e : arr)//{//	t.Erase(e);//	t.InOrder();//}BSTree<int> t1(t);t1.InOrder();
}

test.cpp

#define _CRT_SECURE_NO_WARNINGS 1#include "BinarySelectTree.h"int main()
{test();return 0;
}

4、key 和 key / value( map 和 set 的铺垫 ) 

  在二叉搜索树中,我们都是以 key 作为树排序的依据的,比如整型的插入,结点里存储的整型就是它的键值,也就是我们这里所说的 key ,key 是二叉搜索树排序的依据。

  key的应用场景,比如我们小区的门卫系统,如果车主住在小区,那么业主的汽车的信息就会存入门卫系统,每次只需要按照 key 来查找就可以了(当然小区系统存储肯定用的是数据库,但也可以是二叉搜索树的应用,只不过存储空间小,这样存储的数据都存到内存里了,容易弄丢)。

  接着就是键值对 key / value,这个就是 map 的关键,上面说过,multimap 是支持等值存储的,在 multimap 中,key 和 value 是绑定存储的,比如我们在 multimap 中存储了 3 这个整型,那么键值 key 就是 3 ,如果他只出现了一次,那么 value 就是 1 ,每多存储一个 key ,value 就++,当然,这只是  multimap 的一个应用,value同样可以存储字符串等,这里只是告诉大家两者是绑定的。

  但是有一点大家要知道,不管是 map 和 set ,都只看key,而不去看value。

OK,到这里我们的二叉搜索树就结束了,接下来我们将要面对的是高阶数据结构,让我们一起加油吧。

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

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

相关文章

Profinet转Ethernet IP网关接入五轴车床上下料机械手控制系统的配置实例

本案例为西门子1200PLC借助PROFINET转EtherNet/IP网关与搬运机器人进行连接的配置案例。所需设备包括&#xff1a;西门子1200PLC、Profinet转EtherNet/IP网关以及发那科&#xff08;Fanuc&#xff09;机器人。开启在工业自动化控制领域广泛应用、功能强大且专业的西门子博图配置…

专题二_滑动窗口_长度最小的子数组

引入&#xff1a;滑动窗口首先&#xff0c;这是滑动窗口的第一道题&#xff0c;所以简短的说一下滑动窗口的思路&#xff1a;当我们题目要求找一个满足要求的区间的时候&#xff0c;且这个区间的left和right指针&#xff0c;都只需要同向移动的时候&#xff0c;就可以使用滑动窗…

解锁高效开发:AWS 前端 Web 与移动应用解决方案详解

告别繁杂的部署与运维&#xff0c;AWS 让前端开发者的精力真正聚焦于创造卓越用户体验。在当今快速迭代的数字环境中&#xff0c;Web 与移动应用已成为企业与用户交互的核心。然而&#xff0c;前端开发者常常面临诸多挑战&#xff1a;用户认证的复杂性、后端 API 的集成难题、跨…

北京JAVA基础面试30天打卡04

1. 单例模式的实现方式及线程安全 单例模式&#xff08;Singleton Pattern&#xff09;确保一个类只有一个实例&#xff0c;并提供一个全局访问点。以下是常见的单例模式实现方式&#xff0c;以及如何保证线程安全&#xff1a; 单例模式的实现方式饿汉式&#xff08;Eager Init…

Redis 缓存三大核心问题:穿透、击穿与雪崩的深度解析

引言在现代互联网架构中&#xff0c;缓存是提升系统性能、降低数据库压力的核心手段之一。而 Redis 作为高性能的内存数据库&#xff0c;凭借其丰富的数据结构、灵活的配置选项以及高效的网络模型&#xff0c;已经成为缓存领域的首选工具。本文将从 Redis 的基本原理出发&#…

耘瞳科技国产化点云处理软件,开启智能化三维测量新时代

在现代工业制造领域&#xff0c;三维点云数据已成为推动生产效率提升、质量控制优化以及智能制造转型的关键技术之一。三维点云数据能够提供高精度的物体表面信息&#xff0c;广泛应用于制造零件的质量检测&#xff1b;通过点云数据与CAD模型的对比分析&#xff0c;可以快速检测…

RabbitMQ面试精讲 Day 8:死信队列与延迟队列实现

【RabbitMQ面试精讲 Day 8】死信队列与延迟队列实现 文章标签 RabbitMQ,消息队列,死信队列,延迟队列,面试技巧,分布式系统 文章简述 本文是"RabbitMQ面试精讲"系列第8天&#xff0c;深入讲解死信队列与延迟队列的实现原理与实战应用。文章详细解析死信队列的触发…

团结引擎 1.5.0 版本发布:Android App View 功能详解

核心亮点 原生安卓应用支持 2D & 3D 双形态呈现 编辑器全流程集成 灵活调控功能 多应用并行展示 智能座舱应用示例 快速入门指南 开发说明 功能支持 实验性功能 资源链接 团结引擎 1.5.0 版本已于 4 月 14 日正式上线。本次更新中&#xff0c;车机版引入了一项突…

基于SpringBoot的OA办公系统的设计与实现

文章目录前言详细视频演示具体实现截图后端框架SpringBoot持久层框架MyBaits成功系统案例&#xff1a;代码参考数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续输出高质量…

知识随记-----用 Qt 打造优雅的密码输入框:添加右侧眼睛图标切换显示

Qt 技巧&#xff1a;通过 QLineEdit 右侧眼睛图标实现密码可见性切换 文章目录Qt 技巧&#xff1a;通过 QLineEdit 右侧眼睛图标实现密码可见性切换概要整体架构流程技术名词解释技术细节实现效果展示概要 本文介绍如何使用 Qt 框架为 QLineEdit 控件添加一个右侧的眼睛图标&a…

Unity里的对象旋转数值跳转问题的原理与解决方案

文章目录1. 问题描述2. 问题原因3. 解决方案3.1通过多个父子关系从而控制旋转&#xff08;推荐&#xff09;3.2 使用四元数进行旋转1. 问题描述 我们现在写一个3D的Unity程序&#xff0c;我们现在设置了一个物体后&#xff0c;我们想旋转使其改为我们想要的情况。但是我们如果…

为什么现代 C++ (C++11 及以后) 推荐使用 constexpr和模板 (Templates) 作为宏 (#define) 的替代品?​

我们用现实世界的比喻来深入理解​​为什么 C 中的宏 (#define) 要谨慎使用&#xff0c;以及为什么现代 C (C11 及以后) 推荐使用 constexpr 和模板 (Templates) 作为替代品。​​&#x1f9e9; ​​核心问题&#xff1a;宏 (#define) 是文本替换​​想象宏是一个 ​​“无脑的…

PyCharm vs. VSCode 到底哪个更好用

在 Python 开发者中&#xff0c;关于 PyCharm 和 VSCode 的讨论从未停止。一个是功能齐备的集成开发环境&#xff08;IDE&#xff09;&#xff0c;另一个是轻快灵活的代码编辑器。它们代表了两种不同的开发哲学&#xff0c;选择哪个&#xff0c;往往取决于你的项目需求、个人习…

FPGA学习笔记——VGA彩条显示

目录 一、任务 二、分析 三、代码 四、实验现象 五、更新 一、任务 使用VGA实现彩条显示&#xff0c;模式是640x48060。 二、分析 首先&#xff0c;模式是640x48060&#xff0c;那么对照以下图标&#xff0c;知道其它信息&#xff0c;不清楚时序和VGA扫描方式的可以看看这…

ES-301A :让 Modbus 设备无缝接入工业以太网的高效桥梁

在工业自动化领域&#xff0c;串口设备与以太网的互联互通是提升系统效率的关键。ES-301A 工业以太网串口网关作为上海泗博自动化精心打造的专业解决方案&#xff0c;以强大的协议转换能力、工业级可靠性和灵活配置特性&#xff0c;成为连接 Modbus RTU/ASCII 设备与 Modbus TC…

【学习笔记】FTP库函数学习

【学习笔记】FTP库函数学习 FTP基本指令步骤 1、初始化会话句柄&#xff1a;CURL *curl curl_easy_init(); 2、设置会话选项&#xff1a; 设置服务器地址&#xff0c;设置登录用户和密码 curl_easy_setopt(curl, CURLOPT_URL, ftp_server); curl_easy_setopt(curl, CURLOPT_US…

ARM Cortex-M异常处理高级特性详解

1. 异常处理概述 ARM Cortex-M处理器提供了高效的异常处理机制&#xff0c;包含多种硬件优化特性&#xff0c;显著提升了中断响应性能和系统效率。这些特性对于实时嵌入式系统和网络协议栈&#xff08;如LwIP&#xff09;的性能至关重要。 1.1 Cortex-M异常处理架构 Cortex-M异…

【图像算法 - 08】基于 YOLO11 的抽烟检测系统(包含环境搭建 + 数据集处理 + 模型训练 + 效果对比 + 调参技巧)

一、项目背景与需求 【打怪升级 - 08】基于 YOLO11 的抽烟检测系统&#xff08;包含环境搭建 数据集处理 模型训练 效果对比 调参技巧&#xff09;今天我们使用YOLO11来训练一个抽烟检测系统&#xff0c;基于YOLO11的抽烟检测系统。我们使用了大概两万张图片的数据集训练了…

vue2升级vue3中v-model的写法改造

vue2选项式 <template><div><el-rowclass"group-title":title"$t(restore_default_parameters)">{{ $t(restore_default_parameters) }}</el-row><el-form-item :label"$t(restore_default_parameters)" class"…

5G-LEO 简介

1. 什么是 5G-LEO 5G-LEO 指的是将 5G 新空口&#xff08;5G NR&#xff09;服务扩展到低轨卫星&#xff08;LEO&#xff09;上的非地面网络&#xff08;NTN, Non-Terrestrial Network&#xff09;方案。通过在距地面约500–2 000 km 的低轨道卫星上部署通信载荷&#xff0c;5G…