二叉搜索树K和KV结构模拟

一 什么是二叉搜索树

  这个的结构特性非常重要,是后面函数实现的结构基础,二叉搜索树的特性是每个根节点都比自己的左树任一节点大,比自己的右树任一节点小。

例如这个图,

 41是根节点,要比左树大,比右树小,满足但还不够,还要去看看41的左子树的根和右子树的根是否满足,更要判断这棵树上所有的根节点是不是都满足。而这棵树最厉害的地方之一我们用中序遍历(顺序左根右)便可以知道,遍历结果为13,15,17,22,28,33,37,41,42,50,53,58,61,66,78,排序不就排好了吗,复杂度可媲美快排和归并。二叉搜索树另一个功能那当然就是搜索了,例如我们要找66,66比根节点大,就不用去左子树找了,一下子少遍历一半,然后就去右子树找,和根节点58比较,66比58大,再去右子树找,再比较就找到了,最多查找高度次,满二叉树下为log(n)。而二叉搜索树是不是完美无缺,我也以为已经完美了,不好意思,我太年轻了,直到我看到下面这颗树。

 这个查找一次的效率就退化为O(N)了,解决办法:转化为平衡二叉树,通俗点就是换个根节点重新构造二叉树,例如把5或者7换成根节点,大家可以试试练习一下构建二叉树,构建完后的高度肯定比上图低,查找效率不就高了吗。

   在说二叉搜索树的实现前,我们先说说什么是K结构,什么是KV结构,K结构就是只存一个数据,这个数据称为关键码,例如在英文词库里找一个英文单词,就是用关键码查找,需要的数据也是找到的关键码,但是KV结构就不同了,例如,通过拼音找汉字,这个时候拼音就是关键码,但是我们需要的数据不是拼音这个关键码,而是与之对应的汉字,这就是KV结构。

二 二叉搜索树K结构实现

1 树的节点类

template<class T>struct TreeNode{TreeNode(const T& val):_val(val)//_val可能为自定义类型,在初始化列表初始化方便调用构造函数{;}TreeNode* left = nullptr;TreeNode* right = nullptr;T _val;};

2 BinaryTree树类

查找和排序都封装到了BinaryTree类中,和list一样,将节点类和树类分开封装。

 (1) 默认构造函数

template<class T>class BinaryTree{public:typedef TreeNode<T> Node;BinaryTree(Node*node=nullptr)  该构造函数是用节点指针初始化,也可以再写个构造函数用个BinaryTree对象初始化:_root(node){;}private:Node* _root = nullptr;树类只需根节点地址即可管理整棵树};

  (2)  拷贝构造函数

因为两个copy函数都是用递归遍历二叉树,所以只能再写一个子函数,毕竟外部无法传_root指针。

void copy1(Node*tree)//前序遍历加复用insert拷贝二叉树{if (tree == nullptr)return;insert(tree->_val); 先插入根节点的值,再去左子树和右子树获取节点的数值insert内部会开辟空间copy(tree->left);copy(tree->right);}方法二比较巧妙的是它的第一个参数,root是外部传参_root的引用所以root=new node(),可以直接修改根节点而要拷贝左子树就传root->left的引用,这样new出来的节点可以直接连接到根的_left指针上。右子树同理。void copy2(Node*&root,Node*tree){if (tree== nullptr)return;root = new Node(tree->_val);copy2(root->left,tree->left);copy2(root->right,tree->right);}BinaryTree(const BinaryTree<T>&tree){//copy(tree._root);copy2(_root, tree._root);}

copy2函数传指针引用我是受下面一个成员函数实现的启发,这个传引用一定要好好体会,方便理解后面的函数,非常巧妙。

(3)  赋值

   用的是现代写法,比如t1,t2是两个BinaryTree对象,t1=t2就会调用下面的赋值函数,可是我的参数不是引用,那按规定自定义类型传值传参要用拷贝构造(我在类的成员函数博客曾提及),t2传参给tree就要调用拷贝构造,那tree就是一个新拷贝的对象,我们就可以用swap直接交换tree和t1的根节点指针,并且tree就是一个局部对象,函数调用完后会自动调用析构函数,省去了我们写析构t1树和创建新树的功夫,都给编译器做了。(string模拟的赋值也用到了现代写法)

​void swap(BinaryTree<T>& tree){std::swap(_root, tree._root);}void operator=(BinaryTree<T> tree){swap(tree);}​

(4) find函数

搜索树怎么能缺少搜索功能呢


这个是find函数的子函数,子函数原因和上面同理,都是一开始传参外部无法获取_root,因为递归遍历代码量少,所以我实现的是递归版本bool _findR(Node*root,const T& val){if (root == nullptr)return false;if (val > root->_val)//val比当前_val大,去右树找{return _findR(root->right, val);}else if (val < root->_val)//val比当前_val小,去左树找{return _findR(root->left, val);}else  {return true;//val和当前_val相等,返回true}}//下面这个是外部调用的find函数,只需要传要查找的值即可  bool find(const T& val){return _findR(_root, val);}

我之前在写find函数时,我还想着返回false是不是应该当左树和右树都没找到才返回false,好一会才醒悟,我们之所以去左树找,就是因为要找的val比根节点的值小,那右树更不会有了,所以左树找到nullptr就应该返回false,同理右树找到nullptr也返回false。

(5) insert函数

因为要递归去找合适的位置插入,所以同样要写一个子函数  void _insertR(Node*& root, const T&val){if (root == nullptr)当找到空节点,就可以插入了,此时才是引用起作用的时候{root = new Node(val);  直接就可以修改了,因为root是上一个节点的left或者 right指针的引用。return;}Node* cur = root;if (val > cur->_val)//val大于当前根,插入到右树去{_insertR(cur->right,val);}else if (val < cur->_val)//val小于当前根,插入到左树去{_insertR(cur->left, val);}else{return;}}void insert(const T& val){_insertR(_root, val);}

(6) 中序遍历

		void _Inorder(Node* root)//中序递归遍历{if (root == nullptr)return;_Inorder(root->left);  一直往左子树递归,直到左子树为空,算访问完,可以访问根。cout << root->_val << " "; _Inorder(root->right); 然后去右子树访问,同样分为左子树,根,右子树}void Inorder(){_Inorder(_root);//调用子函数,外部无法获取私有成员_root}

(7) erase函数

     bool _Rerase(Node*&root,const T&val){if (root == nullptr)return false;Node* cur = root;if (val > root->_val)//用_val找节点{return _Rerase(root->right, val);}else if (val < root->_val){return _Rerase(root->left, val);}else//找到了{//该节点只有一个或者无子节点if (root->left == nullptr)  由于root是上一节点左指针或者右指针的别名,所以可以直接拿root->right来赋值给root,否则还要 判断root->right是链接在上一节点的left指针还是 right指针。{root = root->right;}else if (root->right == nullptr){root = root->left;}else{删除有两个子节点的节点-替换法找该节点左子树中最大的,或者右子树中最小的来替换删除节点Node* leftMax = root->left;while (leftMax->right){leftMax = leftMax->right;}std::swap(leftMax->_val, root->_val);return _Rerase(root->left, val);调用_Rerase去删除leftMax节点,这里必须要传root->left,去左子树删除值为val的节点,不能传root例如我们交换leftMax的7和root的8值,如果传root,8的值比7大,就会去右树删,就找不到leftMax节点了,但是root的左子树仍然满足二叉搜索的特性,就可以找到leftMax节点并删除。  }delete cur;  该处统一释放删除节点,并返回truereturn true;}}bool erase(const T& val)//删除某个节点{return _Rerase(_root, val);}

三 kv结构实现

   本来我以为kv结构是要将K结构的树大改,当我实现后才发现,赋值可以直接照搬,,find,insert,erase中大量的if判断都是用关键码判断,根本不需要改动,中序遍历也就多打印一个数据,还有insert和拷贝构造函数要在new一个节点的时候多传一个参数。

接下来就看看一些比较重要的改动,在这里_key存关键码,而我上面二叉树K结构中是_val存的关键码,不要搞混了。

1 树的节点类

     template<class T,class K>struct TreeNode{TreeNode(const T& val,const K&key):_val(val),_key(key){;}             类内可不加模板参数,也就是说TreeNode等价于TreeNode<T,k>TreeNode* left = nullptr; TreeNode* right = nullptr;T _val;//存与关键码对应的数据K _key;//_key存关键码};

2 BinaryTree类

有了先前K结构树的基础,这里构造和析构函数我们就很好理解。

(1)构造和析构函数

template<class T, class K>class BinaryTree{public:typedef TreeNode<T,K> Node;BinaryTree(Node* node = nullptr)  默认构造无改变:_root(node){;}void _DestroyR(Node*&root)  递归释放节点,采用后序遍历的方式delete{if (root == nullptr)return;_DestroyR(root->left);_DestroyR(root->right);delete root;root = nullptr;}~BinaryTree(){_DestroyR(_root);}private:Node* _root = nullptr;   成员变量是不变的,毕竟kv结构的树用根节点同样可以管理};
}

(3)erase函数

  bool _Rerase(Node*& root, const T& key){if (root == nullptr)return false;Node* cur = root; //记录节点,方便后面deleteif (key > root->_key){return _Rerase(root->right,key);}else if (key <root->_key){return _Rerase(root->left, key);}else//找到了{//该节点只有一个或者无子节点if (root->left == nullptr){root = root->right;}else if (root->right == nullptr){root = root->left;}else{Node* leftMax = root->left;while (leftMax->right){leftMax = leftMax->right;}std::swap(leftMax->_val, root->_val); 在交换时要多交换一个值std::swap(leftMax->_key, root->_key);return _Rerase(root->left, key); 并且还是用key值去找leftMax删}  删除完leftMax后就直接return了,就不会重复删除。delete cur;return true;}}bool erase(const T& val)//删除某个节点{return _Rerase(_root, val);}

二叉搜索树中最复杂的就是erase函数,大家在此处一定要画图理解。

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

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

相关文章

人工智能原理概述 - ChatGPT 背后的故事

大家好&#xff0c;我是比特桃。如果说 2023 年最火的事情是什么&#xff0c;毫无疑问就是由 ChatGPT 所引领的AI浪潮。今年无论是平日的各种媒体、工作中接触到的项目还是生活中大家讨论的热点&#xff0c;都离不开AI。其实对于互联网行业来说&#xff0c;自从深度学习出来后就…

进入现代云技术的世界-APIGateway、ServiceMesh、OpenStack、异步化框架、云原生框架、命令式API与声明式API

目录 APIGateway Service Mesh OpenStack 异步化框架 云原生框架 命令式API与声明式API APIGateway API网关&#xff08;API Gateway&#xff09;是一个服务器——充当了客户端和内部服务之间的中间层。API网关负责处理API请求&#xff0c;将客户端的请求路由到相应的后端…

StringJoiner

1、为什么要学习StringJoiner&#xff1f; 2、StringJoiner概述 StringJoiner跟StringBuilder一样&#xff0c;也可以看成一个容器&#xff0c;创建之后里面的内容是可变的。 2.1、作用 提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有…

银行家算法

1.设计目的与要求 1.1设计目的 了解银行家算法中使用的数据结构和求安全序列算法&#xff0c;并进一步加深对避免死锁算法及其实现过程的理解。 1.2设计要求 通过编写和调试一个系统动态分配资源的简单模拟程序&#xff0c;观察死锁产生的条件&#xff0c;并采用适当的算法&a…

2023.8.7论文阅读

文章目录 CMUNeXt: An Efficient Medical Image Segmentation Network based on Large Kernel and Skip Fusion摘要本文方法实验结果 Boundary Difference Over Union Loss For Medical Image Segmentation&#xff08;损失函数&#xff09;摘要本文方法实验结果 CMUNeXt: An E…

回归预测 | MATLAB实现基于PSO-LSSVM-Adaboost粒子群算法优化最小二乘支持向量机结合AdaBoost多输入单输出回归预测

回归预测 | MATLAB实现基于PSO-LSSVM-Adaboost粒子群算法优化最小二乘支持向量机结合AdaBoost多输入单输出回归预测 目录 回归预测 | MATLAB实现基于PSO-LSSVM-Adaboost粒子群算法优化最小二乘支持向量机结合AdaBoost多输入单输出回归预测预测效果基本介绍模型描述程序设计参考…

【基础操作】Linux打开terminal,Anaconda默认进入的虚拟环境(python版本)设置(自行指定)

为了免除每次打开terminal都要输入 conda activate … 的麻烦&#xff0c;可以这么设置。 1. 打开terminal&#xff0c;然后输入命令 vim ~/.bashrc2. 然后在文件末尾添加 conda activate your_envs # your_envs是你的虚拟环境名称3. 保存退出&#xff0c;重新打开就成功啦…

navicat连接postgresql报错

navicat连接postgresql报错 navicat连接postgresql报错 现象 有小伙伴告诉我 安装了新的postgresql 使用navicat连接&#xff0c;报错 ERROR: column "datlastsysoid" does not existLINE 1: SELECT DISTINCT datlastsysoid FROM pg database column “datlastsy…

.netcore grpc客户端流方法详解

一、客户端流式处理概述 客户端流式处理方法在该方法没有接收消息的情况下启动。 requestStream 参数用于从客户端读取消息。 返回响应消息时&#xff0c;客户端流式处理调用完成。客户端可以发送多个消息流到服务端&#xff0c;当所有客户端消息流发送结束&#xff0c;调用请…

SpringBoot案例-部门管理-修改

目录 前言 查看页面原型&#xff0c;明确需求 页面原型 需求 阅读接口文件 思路分析 功能接口开发 控制层&#xff08;Controller类&#xff09; 业务层&#xff08;Service类&#xff09; 业务类 业务实现类 持久层&#xff08;Mapper类&#xff09; 接口测试 前…

ROS2 学习(一)介绍,环境搭建,以及个人安装的一些建议

ROS2 学习 学习自b站课程&#xff1a;https://www.bilibili.com/video/BV16B4y1Q7jQ?p1 &#xff08;up主&#xff1a;古月居GYH&#xff09; ROS 介绍 Robot OS&#xff0c;为机器人开发提供了相对完善的 middleware&#xff0c;工具&#xff0c;软件等。 ROS1 对嵌入式设…

计算机网络(7) --- UDP协议和TCP协议

计算机网络&#xff08;6&#xff09; --- https协议_哈里沃克的博客-CSDN博客https协议https://blog.csdn.net/m0_63488627/article/details/132112683?spm1001.2014.3001.5501 目录 1.补充知识 1.PORT端口号 2.端口号范围划分 3.知名端口号 2.UDP协议 1.UDP报头 2.U…

BGP小综合

实验题目如下&#xff1a; 实验拓扑如下&#xff1a; 实验要求如下&#xff1a; 【1】R2-7每台路由器均存在一个环回接口用于建立邻居&#xff0c;同时还存在一个环回来代表连接用户的 接口;最终这些连接用户的接口网络需要可以和R1/8的环回通讯 【2】AS2网段地址1…

基于smardaten无代码开发智能巡检系统,让无人机飞得更准

目录 引言需求背景搭建思路开发过程&#xff08;1&#xff09;无人机设备数据接入&#xff08;2&#xff09;无人机巡检任务管理&#xff08;3&#xff09;无人机三维防控监视&#xff08;4&#xff09;运防一体化大屏设计&#xff08;5&#xff09;异常告警管理&#xff08;6&…

面试总结-webpack/git

说说你对webpack的理解 webpack 是一个静态模块打包器&#xff0c;整个打包过程就像是一条生产线&#xff0c;把资源从入口放进去&#xff0c;经过一系列的加工&#xff08;loader&#xff09;&#xff0c;最终转换成我们想要的结果&#xff0c;整个加工过程还会有监控&#x…

redis基础(三十六)

安装redis、配置redis 目录 一、 概述 &#xff08;一&#xff09;NoSQL 1、类型 2、应用场景 &#xff08;二&#xff09;Redis 二、安装 &#xff08;一&#xff09;编译安装 &#xff08;二&#xff09;RPM安装 三、目录结构 四、命令解析 五、redis登录更改 1、…

2023国赛数学建模C题思路分析

文章目录 0 赛题思路1 竞赛信息2 竞赛时间3 建模常见问题类型3.1 分类问题3.2 优化问题3.3 预测问题3.4 评价问题 4 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 竞赛信息 全国大学生数学建模…

中睿天下入选河南省网信系统2023年度网络安全技术支撑单位

近日&#xff0c;河南省委网信办发布了“河南省网信系统2023年度网络安全技术支撑单位名单”&#xff0c;中睿天下凭借出色的网络安全技术能力和优势成功入选。 本次遴选由河南省委网信办会同国家计算机网络与信息安全管理中心河南分中心&#xff08;以下简称安全中心河南分中心…

Zebec Protocol 将进军尼泊尔市场,通过 Zebec Card 推动地区金融平等

流支付正在成为一种全新的支付形态&#xff0c;Zebec Protocol 作为流支付的主要推崇者&#xff0c;正在积极的推动该支付方案向更广泛的应用场景拓展。目前&#xff0c;Zebec Protocol 成功的将流支付应用在薪酬支付领域&#xff0c;并通过收购 WageLink 将其纳入旗下&#xf…

Pytest测试框架3

目录&#xff1a; pytest结合数据驱动-yamlpytest结合数据驱动-excelpytest结合数据驱动-csvpytest结合数据驱动-jsonpytest测试用例生命周期管理&#xff08;一&#xff09;pytest测试用例生命周期管理&#xff08;二&#xff09;pytest测试用例生命周期管理&#xff08;三&a…