【C++进阶】二叉搜索树(来自二叉树的复仇)

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

 主厨的主页:Chef‘s blog  

 所属专栏:c++大冒险
 

 总有光环在陨落,总有新星在闪烁


[本节目标]

1. 二叉搜索树的介绍

2. 二叉搜索树的实现

3.二叉树搜索树应用

一.二叉树的介绍

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

2. 二叉搜索树的实现 

本次二叉搜索树一律是小数放左边,大数放右边。

2.1节点

结合二叉树的知识,我们只知道二叉搜索树是分别写了两个类,一个是节点的类,一个是整个二叉树的类

template<class T>
struct BSTNode
{BSTNode(const T& val = T()):_val (val),_left(nullptr),_right(nullptr){}BSTNode<T>* _left;BSTNode<T>* _right;T _val;
};

注意事项:

             无论何时调用类模板,都要把模板参数给出

2.2成员变量

template<class T>
class BSTree
{typedef BSTNode<T> Node;
private:Node* _node;
};

注意事项:

              为了方便等会调用节点模板,我们先把他typedef为Node

2.3构造函数

BSTree()
:_node(nullptr)
{}

简简单单的用nullptr初始化一波

2.4拷贝构造函数

BSTree(const BSTree<T>& b)
{_root = Copy(b._node);
}
Node* Copy(Node *b)
{if (b == nullptr)return nullptr;Node* p = new Node(b->_val);p->_left = Copy(b->_left);p->_right = Copy(b->_right);return p;
}

注意事项:

  1.  为了方便用递归,我们用别的函数实现拷贝,在用拷贝构造函数调用那个函数(捡便宜属于是)。
  2. Copy用的是前序遍历来实现的

2.5赋值操作符重载

BSTree<T>& operator=(BSTree<T>b)
{swap(b._node, _node);return *this;
}

注意事项:

              这次我们依旧用摩登的实现方法,传过去一份临时拷贝,在交换根节点即可。

2.6析构函数

~BSTree()
{destory(_node);
}
void destory(Node* node)
{if (node == nullptr)return;if (node->_left)destory(node->_left);if (node->_right)destory(node->_right);delete node;
}

注意事项:

  1. 同样的道理,我们也是又写了一个函数去实现销毁功能,再让析构函数调用,主要原因还是析构函数的接口不合适
  2. destory用的是后序遍历

2.7中序遍历二叉树

	void _InOrder(Node* node){if(node->_left)_InOrder(node->_left);cout << node->_val << endl;if(node->_right)_InOrder(node->_right);}void InOrder(){_InOrder(_node);}

注意事项:

               中序遍历可以得到二叉搜索树的升序或降序排列,如图

2.8查找

查找是二叉搜索树的一大优势,毕竟这log N的速度谁不爱呢?

2.8.1迭代实现

Node* Find(const T& val)
{Node* node = _node;while (node){if (node->_val == val)return node;else if (node->_val > val)node = node->_left;elsenode = node->_right;}return nullptr;
}

注意事项:

  1. 当前节点的val大于目标val是去左边找
  2. 当前节点的val小于目标val是去右边找
  3. 结果找到了就返回节点的指针,否则返回nullptr

2.8.2递归实现

bool RFind(const T& val)
{return _RFind(_node, val);
}
bool _RFind(Node*node,const T& val)
{if (node == nullptr)return false;if (val == node->_val)return true;return _RFind(node->_left, val) || _RFind(node->_left, val);}

注意事项:

               这个返回指针很困难,所以我们直接返回真假表示有无该节点

2.9插入

2.9.1迭代

bool Insert(const T& val)
{if (_node == nullptr){_node = new Node(val);}else{Node* parent = nullptr;Node* child = _node;Node* ptr = new Node(val);while (child){parent = child;if (_node->_val > val){child = child->_left;}else if (_node->_val < val){child = child->_right;}elsereturn false;}if (parent->_val > val)parent->_left = ptr;if (parent->_val < val)parent->_right = ptr;}return true;
}

注意事项:

  • 1.我们传的是引用,当发现根节点是空时可以直接修改根节点而不用搞二级指针
  • 2.我们定义父亲节点和孩子节点,在循环中找到要把值放到那个父亲节点的孩子里
  • 3.判断是放到父亲的左节点还是右节点。
  • 4.如果发现该值已经存在了,则返回false,否则插入成功,返回true

2.9.2递归

bool RInsert(const T& val)
{_RInsert(_node, val);
}
bool _RInsert(Node*& p, const T& val)
{if (p == nullptr){p = new Node(val);return true;}if (p->_val > val)return RInsert(p->_left, val);else if (p->_val < val)return RInsert(p->_right, val);elsereturn false;
}

注意事项:

  • 1.还是函数调用另一个函数(我们成为工具人)
  • 2.我们传的是引用,所以不需要父亲节点了,直接修改即可
  • 3.果发现该值已经存在了,则返回false,否则插入成功,返回true

2.10删除(重难点)

2.10.1迭代

bool Erase(const T& val)
{if (_node == nullptr)return true;Node* cur = _node;Node* parent_node = nullptr;while (cur){if (cur->_val == val)break;else if (cur->_val > val){parent_node = cur;cur = cur->_left;}else{parent_node = cur;cur = cur->_right;}}if (cur == nullptr)return false;if (cur->_left == nullptr){if (parent_node == nullptr)_node = _node->_right;else if (parent_node->_left == cur)parent_node->_left = cur->_right;else if (parent_node->_right == cur)parent_node->_right = cur->_right;delete cur;}else if (cur->_right == nullptr){if (parent_node == nullptr)_node = _node->_left;else if (parent_node->_left == cur)parent_node->_left = cur->_left;elseparent_node->_right = cur->_left;delete cur;}else{Node* leftmax = cur->_left;Node* parent_leftmax = cur;while (leftmax->_right){parent_leftmax = leftmax;leftmax = leftmax->_right;}cur->_val = leftmax->_val;if (parent_leftmax->_left)parent_leftmax->_left = leftmax->_left;elseparent_leftmax->_right = leftmax->_left;delete leftmax;}
}
首先查找元素是否在二叉搜索树中,如果不存在,则返回 , 否则要删除的结点可能分下面四种情
况:
  • a. 要删除的结点无孩子结点
  • b. 要删除的结点只有左孩子结点
  • c. 要删除的结点只有右孩子结点
  • d. 要删除的结点有左、右孩子结点
看起来有待删除节点有 4 中情况,实际情况 a 可以与情况 b 或者 c 合并起来,因此真正的删除过程
如下:
  • 情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
  • 情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
  • 情况d:在它的左子树中寻找最大的节点a,用它的值填补到被删除节点中,再来处理结点a--替换法删除

在此基础上,我们还要对每种情况下删除节点是否为根节点进行讨论

2.10.2递归

	bool _RErase(Node*&node,const T&val){if (node == nullptr)return false;if (node->_val > val)return _RErase(node->_left, val);if (node->_val < val)return _RErase(node->_right, val);else{if (node->_left == nullptr){Node* right = node->_right;delete node;node = right;}else if (node->_right == nullptr){Node* left = node->_left;delete node;node =left;}else{Node* p = node->_left;while (p->_right){p = p->_right;}swap(node->_val , p->_val);return  _RErase(node->_left,val);}return true;}}bool RErase(const T& val){return _RErase(_node,val);}

注意事项:

  • 1.由于使用了引用,对左子树为空或右子树为空不再需要父亲节点的帮助
  • 2.若左右子树都不为空,则交换letfmax和node数值后,通过递归消除leftmax
  • 3.最后递归传参不要直接传根节点,而是node->left,因为之前的交换打乱了二叉树的结构,只能确定node->_left这棵树还是结构正确的

三.二叉树搜索树应用

3.1K模型

K 模型即只有 key 作为关键码,结构中 只需要存储Key 即可,关键码即为需要搜索到 的值
比如: 给一个单词 word ,判断该单词是否拼写正确 ,具体方式如下:
  1. 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  2. 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

3.2. KV模型:

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对 。该种方式在现实生活中非常常见:
  1. 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英 文单词与其对应的中文<word, chinese>就构成一种键值对;
  2. 比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出 现次数就是<word, count>就构成一种键值对
// 改造二叉搜索树为KV结构
template<class K, class V>
struct BSTNode{BSTNode(const K& key = K(), const V& value = V()): _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value){}BSTNode<T>* _pLeft;BSTNode<T>* _pRight;K _key;V _value};
template<class K, class V>
class BSTree{typedef BSTNode<K, V> Node;typedef Node* PNode;
public:BSTree(): _pRoot(nullptr){}PNode Find(const K& key);bool Insert(const K& key, const V& value)bool Erase(const K& key)
private:PNode _pRoot;};
void TestBSTree3()
{// 输入单词,查找单词对应的中文翻译BSTree<string, string> dict;dict.Insert("string", "字符串");dict.Insert("tree", "树");dict.Insert("left", "左边、剩余");dict.Insert("right", "右边");dict.Insert("sort", "排序");// 插入词库中所有单词string str;while (cin>>str){BSTreeNode<string, string>* ret = dict.Find(str);if (ret == nullptr){cout << "单词拼写错误,词库中没有这个单词:" <<str <<endl;}else{cout << str << "中文翻译:" << ret->_value << endl;}}
}
void TestBSTree4()
{// 统计水果出现的次数string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", 
"苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (const auto& str : arr){// 先查找水果在不在搜索树中// 1、不在,说明水果第一次出现,则插入<水果, 1>// 2、在,则查找到的节点中水果对应的次数++//BSTreeNode<string, int>* ret = countTree.Find(str);auto ret = countTree.Find(str);if (ret == NULL){countTree.Insert(str, 1);}else{ret->_value++;}}countTree.InOrder();
}

总结:

今天我们学习了二叉树里的扛把子——二叉搜索树,细致地模拟了他的接口的实现(递归与迭代),接着讲解了他的应用——K模型和KV模型,最后把KV写了一遍。

觉得有帮助就点赞关注支持一下吧

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

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

相关文章

MyBatis 参数重复打印的bug

现象 最近有个需求&#xff0c;需要在mybatis对数据库进行写入操作的时候&#xff0c;根据条件对对象中的某个值进行置空&#xff0c;然后再进行写入&#xff0c;这样数据库中的值就会为空了。 根据网上查看的资料&#xff0c;选择在 StatementHandler 类执行 update 的时候进…

C++之调用Python

1、配置头文件 Python安装目录下的include目录加入头文件目录。Visual Studio2022中操作路径是&#xff1a;属性–> C/C -> 常规-> 附加包含目录 C:\Users \AppData\Local\Programs\Python\Python39\include 2、配置lib库目录 要将Python39.lib加入编译链接。Visua…

neo4j使用详解(七、cypher数学函数语法——最全参考)

Neo4j系列导航&#xff1a; neo4j及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 5.数学函数 5.1.数值函数 数学函数仅对数字表达式进行运算&#xff0c;如果对任何其他值使用&#xff0c;将返回错误 abs()&#xf…

Nginx 基础

文章目录 Nginx概念安装下载上传安装包执行准备条件指定安装位置编译和安装启动服务创建启动脚本 linux文件目录nginx运行原理nginx配置域名概念和原理域名配置 Nginx 概念 Nginx 是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是…

【Java八股面试系列】Arraylist和HashMap的底层原理

文章目录 ArrayList源码总&#xff1a;构造方法扩容机制remove HashMap总&#xff1a;构造方法细节问题putVal()方法resize()方法Hash值 HashMap常见问题 ConcurrentHashMap总&#xff1a;putVal()方法自己的测试 为什么重写HashCode和equals ArrayList源码 总&#xff1a; *…

3.28号arm

can总线相关理论 1. 概念 控制器局域网&#xff08;Controller Area Network&#xff0c;CAN&#xff09;&#xff0c;其特点是可拓展性好&#xff0c;可承受大量数据的高速通信&#xff0c;高度稳定可靠&#xff0c;因此常应用于汽车电子领域、工业自动化、医疗设备等高要求…

Java JSON字符串相关问题

一、依赖包 <!--json包--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.15</version></dependency> 二、举例 1.实体对象转Json字符串 1.1 代码实现 Dog.java: pack…

python_web1(前端开发之HTML、CSS、Bootstap、Javascript、JQuery)

文章目录 一、Flask网页开发1.1创建一个名为web1.py的python文件1.2 templates目录创建文件index.html 二、html标签2.1 编码2.2title < head >2.3 标题< h>2.4 div和span2.5超链接1.在index.xml文件中补充。2.修改web1.py文件3.添加get_self.html4.效果 2.6图片1.…

Java 堆外内存及调优

文章目录 直接内存简介为什么DirectByteBuffer可以优化 IO 性能 直接内存的分配直接内存的回收直接内存跟踪与诊断 直接内存简介 直接内存(Direct Memory) 并不是虚拟机运行时数据区的一部分&#xff0c;并非Java虚拟机规范中定义的内存区域。但是这部分内存的频繁使用&#x…

【LeetCode】三月题解

文章目录 [2369. 检查数组是否存在有效划分](https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/)思路&#xff1a;代码&#xff1a; [1976. 到达目的地的方案数](https://leetcode.cn/problems/number-of-ways-to-arrive-at-destination/) 思路…

C++教学——从入门到精通 5.单精度实数float

众所周知&#xff0c;三角形的面积公式是(底*高)/2 那就来做个三角形面积计算器吧 到吗如下 #include"bits/stdc.h" using namespace std; int main(){int a,b;cin>>a>>b;cout<<(a*b)/2; } 这不对呀&#xff0c;明明是7.5而他却是7&#xff0c;…

让IIS支持.NET Web Api PUT和DELETE请求

前言 有很长一段时间没有使用过IIS来托管应用了&#xff0c;今天用IIS来托管一个比较老的.NET Fx4.6的项目。发布到线上后居然一直调用不同本地却一直是正常的&#xff0c;关键是POST和GET请求都是正常的&#xff0c;只有PUT和DELETE请求是有问题的。经过一番思考忽然想起来了I…

YOLOv9改进策略 :主干优化 | 极简的神经网络VanillaBlock 实现涨点 |华为诺亚 VanillaNet

💡💡💡本文改进内容: VanillaNet,是一种设计优雅的神经网络架构, 通过避免高深度、shortcuts和自注意力等复杂操作,VanillaNet 简洁明了但功能强大。 💡💡💡引入VanillaBlock GFLOPs从原始的238.9降低至 165.0 ,保持轻量级的同时在多个数据集验证能够高效涨点…

每日学习笔记:C++ STL算法分类

非更易型 更易型 移除型 变序型 排序型 已排序区间算法 数值型算法

【滑动窗口】Leetcode 将 x 减到 0 的最小操作数

题目解析 1658. 将 x 减到 0 的最小操作数 算法讲解 这道题按照题目要求的话会变得很难&#xff0c;因为不仅需要考虑数字减到0&#xff0c;还需要考虑最小的操作数。正难则反&#xff0c;按照这个思路&#xff0c;我们来解析题目 这道题本质上无非就是在左边寻找一段区间&a…

HCIP第三次作业(综合)

一、实验要求 二、实验步骤 1、配置IP地址部分 PC1&#xff1a; PC2&#xff1a; PC3&#xff1a; PC4&#xff1a; R1&#xff1a; R2&#xff1a; R3&#xff1a; R4&#xff1a; R5&#xff1a; 环回&#xff1a; 2.通过配置缺省路由让公网互通 [R1]ip route-static 0.0.…

代码随想录第27天| 39. 组合总和

39. 组合总和 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.com) 带你学透回溯算法-组合总和&#xff08;对应「leetcode」力扣题目&#xff1a;39.组合总和&#xff09;| 回溯法精讲&#xff01;_哔哩哔哩_bilibili 给你一个 无重复元…

思考:开启MMU瞬间可能出现的多种问题以及多种解决方案

快速链接: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; (说明本文的介绍都是基于armv8-aarch64或armv9硬件架构) 在mmu未开启阶段&#xff0c;PC操作的都是物理地址执行程序&#xff0c;这样看起来一切正常&#xff0c;没啥问题。 例如…

Windows Server 2022 使用ApacheDS用户远程桌面登录服务器

Windows Server 2022 使用ApacheDS用户远程桌面登录服务器 1、接上篇 Windows Server 2022 使用ApacheDS用户认证 使用Administrator用户远程登录192.168.1.100windows server&#xff0c;打开pGina软件 2、输入刚刚在ApacheDS中的新添加的用户测试一下&#xff0c;会自动添加…

如何在极狐GitLab 配置 邮件功能

本文作者&#xff1a;徐晓伟 GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文主要讲述了在极狐GitLab 用户…