【C++高阶】高效搜索的秘密:深入解析搜索二叉树

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:C++多态
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀二叉搜索树

  • 📒1. 二叉搜索树
    • 🎩二叉搜索树概念
    • 🎈二叉搜索树操作
  • 📕2. 二叉搜索树模拟实现
    • 🧩二叉搜索树结构
    • 🧩二叉搜索树操作
      • 🌈插入
      • 🌞遍历
      • 🌙查找
      • ⭐删除
    • 🧩二叉搜索树默认成员函数
  • 📜3. 二叉搜索树模拟实现(递归)
    • 🌞插入
    • 🌙查找
    • ⭐删除
  • 📚4. 二叉搜索树的应用
    • 🍁KV模型
    • 🍂KV模型实现
      • 💧英汉词典
      • 🔥计数
    • 🌄二叉树巩固知识
  • 📖5. 总结


前言: 在数据结构和算法的广阔领域中,二叉搜索树(Binary Search Tree,简称BST)无疑是一颗璀璨的明星。它以其高效的数据检索能力和独特的树形结构,在计算机科学领域扮演着举足轻重的角色。对于任何对编程和数据结构感兴趣的人来说,掌握二叉搜索树都是至关重要的一步

二叉搜索树不仅仅是一个简单的数据结构,它更是一种解决问题的方式和思维的体现。通过维护二叉树中每个节点的左子树所有值均小于它的值,右子树所有值均大于它的值的特性,二叉搜索树在插入、查找和删除操作中展现出了卓越的性能。这种特性使得二叉搜索树在各种应用中成为了一种理想的数据结构选择,从基础的算法练习到复杂的系统优化,都能见到它的身影

学习二叉搜索树并非易事。它需要我们深入理解其性质、原理和算法实现。我们需要掌握如何构建一棵二叉搜索树,如何遍历它,以及如何在其中进行高效的查找、插入和删除操作。这些都需要我们付出大量的时间和精力去学习和实践。我们将从二叉搜索树的基本概念出发,逐步深入到其性质、构建、遍历以及操作的实现

让我们一起踏上学习二叉搜索树的旅程,探索它带来的无尽可能!
(本文重在二叉搜索树的模拟实现与理解)


📒1. 二叉搜索树

🎩二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

在这里插入图片描述


🎈二叉搜索树操作

首先,在二叉搜索树的操作中只支持插入,查找,删除,遍历,并不支持修改操作,因为在修改后谁也不能保证它依然是一棵二叉搜索树,二叉搜索树的时间复杂度范围在(O(logN)~O(N))

在二叉搜索树的遍历中一般采用中序遍历: 先遍历左子树,然后访问根节点,最后遍历右子树。在BST中,中序遍历会按照升序访问所有节点

二叉搜索树示例
在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

📕2. 二叉搜索树模拟实现

🧩二叉搜索树结构

在这里插入图片描述

二叉搜索树结构的和树形结构差不多,这意味着每个元素(通常称为节点)都有两个指针:一个指向前一个左子树,另一个指向右子树,因此我们需要单独再定义一个类来表示节点结构,每个节点再串联起来构成BST

(在模拟实现二叉搜索树时,不用定义命名空间,因为不会和库中发生冲突)

节点定义(示例):

template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key = K()):_key(key), _left(nullptr), _right(nullptr){}
};

BST定义(示例):

template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:// 构造函数等可能的其他成员函数... 
private:Node* _root = nullptr;
};

🧩二叉搜索树操作

🌈插入

插入的具体过程如下:

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新节点

在这里插入图片描述
插入代码实现(示例):

bool Insert(const K& key)
{// 当根为空时,直接插入if (_root == nullptr){_root = new Node(key);return true;}// 定义parent是因为,在最后找到插入位置时,需要parent将节点进行连接Node* parent = nullptr; Node* cur = _root;while (cur){parent = cur;// 插入的值比cur位置大时,cur往右移动if (key > cur->_key){cur = cur->_right;}// 插入的值比cur位置小时,cur往左移动else if (key < cur->_key){cur = cur->_left;}// 当插入的值和cur位置相等时,直接退出,因为二叉搜索树不允许有相同的元素else{return false;}}// 将插入位置的新节点与二叉搜索树连接cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}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* cur = _root;while (cur){// 查找的值比cur大,cur往右移动if (key > cur->_key){cur = cur->_right;}// 查找的值比cur小,cur往左移动else if (key < cur->_key){cur = cur->_left;}// 找到key,返回trueelse{return true;}}return false; // 找不到key,返回false
}

⭐删除

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

  • 要删除的结点无孩子结点
  • 要删除的结点只有左孩子结点
  • 要删除的结点只有右孩子结点
  • 要删除的结点有左、右孩子结点

而上面四种情况可以转化成下面的情况:

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

在这里插入图片描述

这三种情况就是我们模拟实现删除的方法!

删除代码实现(示例):

bool Erase(const K& key)
{Node* cur = _root;Node* parent = nullptr;while (cur){parent = cur;if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{// 准备删除// 左为空if (cur->_left == nullptr){// 当需要删除的是头节点时if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}// 右为空else if (cur->_right == nullptr){// 当需要删除的是头节点时if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}// 两边都不为空else{// 这里与外层不是同一块作用域,所以可以再次定义parent,不把parent定义为nullptr是因为//,可能不进入下面循环导致对parent空指针的使用Node* subLeft = cur->_right; // 定义右数节点Node* parent = cur;while (subLeft->_left){parent = subLeft;subLeft = subLeft->_left;}swap(cur->_key, subLeft->_key); // 替换右子树的最左节点if (subLeft == parent->_left){// 最左节点肯定没有左孩子,所以让parent接管它的右子树parent->_left = subLeft->_right;}else{parent->_right = subLeft->_right;}delete subLeft;}return true;}}return false;
}

🧩二叉搜索树默认成员函数

构造

BSTree() = default; // 显式地定义默认构造函数  

拷贝构造

BSTree(const BSTree<K>& t)
{_root = Copy(t._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;
}

赋值重载

BSTree<K>& operator=(BSTree<K> t)
{// 现代写法-> 直接调用swapswap(_root, t._root);return *this;
}

析构

~BSTree()
{Destory(_root);
}void Destory(Node*& _root)
{if (_root == nullptr){return;}// 递归调用析构Destory(_root->_left);Destory(_root->_right);delete _root;_root == nullptr;
}

📜3. 二叉搜索树模拟实现(递归)

在进行递归操作的模拟实现时,一般都要传节点,进行多层的调用,因为我们都要定义两层

bool FindR(const K& key)
{return _FindR(_root, key);
}bool InsertR(const K& key)
{return _InsertR(_root, key);
}bool EraseR(const K& key)
{return _EraseR(_root, key);
}

🌞插入

代码实现(示例):

bool _InsertR(Node*& _root, const K& key)
{// 递归出口if (_root == nullptr){// 这里我们无需在进行对新节点的连接,因为我们是传引用传参,_root = new Node(key);return true;}if (key > _root->_key){return _InsertR(_root->_right, key);}else if (key < _root->_key){return _InsertR(_root->_left, key);}else{return false;}
}

🌙查找

代码实现(示例):

bool _FindR(Node* _root, const K& key){if (_root == nullptr){return false;}if (key > _root->_key){return _FindR(_root->_right, key);}else if (key < _root->_key){return _FindR(_root->_left, key);}else{return true;}}

⭐删除

代码实现(示例):

bool _EraseR(Node*& _root, const K& key)
{if (_root == nullptr){return false;}if (key > _root->_key){return _EraseR(_root->_right, key);}else if (key < _root->_key){return _EraseR(_root->_left, key);}else{// 删除if (_root->_left == nullptr){Node* del = _root;_root = _root->_right;delete del;return true;}else if (_root->_right == nullptr){Node* del = _root;_root = _root->_left;delete del;return true;}else{Node* subLeft = _root->_right;while (subLeft->_left){subLeft = subLeft->_left;}swap(_root->_key, subLeft->_key);// 让子树继续递归下去return _EraseR(_root->_right, key);}}
}

📚4. 二叉搜索树的应用

🍁KV模型

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
    文单词与其对应的中文<word, chinese>就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
    现次数就是<word, count>就构成一种键值对
namespace kv // 避免与之前k模型冲突
{template<class K, class V>struct BSTreeNode{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;V _value;BSTreeNode(const K& key = K(), const V& value = V()): _left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:// 构造函数等可能的其他成员函数... // 在成员函数中,我们只需要在insert中加入value元素即可private:Node* _root = nullptr;};
}

在成员函数中,我们只需要在Insert中加入value元素即可

🍂KV模型实现

💧英汉词典

代码实现(示例):

int main()
{kv::BSTree<string, string> dict;dict.insert("left", "左边、剩余");dict.insert("string", "字符串");dict.insert("hahaha", "哈哈");dict.insert("Eternity", "永恒");dict.insert("sort", "排序");dict.InOrder();string str;while (cin >> str){kv::BSTreeNode<string, string>* ret = dict.Find(str);if (ret){cout << ret->_value << endl;}else{cout << "无此单词" << endl;}}
}

🔥计数

代码实现(示例):

int main()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };kv::BSTree<string, int> countTree;for (auto& e : arr){kv::BSTreeNode<string, int>* ret = countTree.Find(e);if (ret == nullptr){countTree.insert(e, 1);}else{ret->_value++;}}countTree.InOrder();return 0;
}

🌄二叉树巩固知识

最后在这给大家推荐几道巩固二叉树的编程题
二叉树创建字符串
二叉树的分层遍历
找到树中两个指定节点的最近公共祖先
二叉树搜索树转换成排序双向链表
根据一棵树的前序遍历与中序遍历构造二叉树
二叉树中序遍历 ,非递归迭代实现


📖5. 总结

经过我们一同对搜索二叉树的深入学习和探索,相信你已经对这种数据结构有了全面而深刻的理解。搜索二叉树以其独特的性质在数据检索领域展现了出色的性能,无论是插入、删除还是查找操作,都体现了其高效和灵活的特点

学习的道路永无止境。虽然我们已经走过了搜索二叉树的基本概念和操作的学习之旅,但这只是你编程旅程中的一个站点。前方还有更多数据结构等待你去探索,更多算法等待你去实践

不要忘记持续学习和自我提升。计算机科学是一个日新月异的领域,新的技术和算法不断涌现。只有保持对知识的渴望和追求,我们才能在这个领域中不断前行,让我们一起在学习的道路上不断前行!
在这里插入图片描述
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

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

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

相关文章

Java——构造器(构造方法)和 this

一、什么是构造器 构造器&#xff08;Constructor&#xff09;是Java类的一种特殊方法&#xff0c;用于初始化对象的状态。构造器在创建对象时被调用&#xff0c;可以对对象的成员变量进行初始化。 我之前的文章《Java——类和对象-CSDN博客》中也提到了构造器。 二、构造器…

银行数仓项目实战(四)--了解银行业务(存款)

文章目录 项目准备存款活期定期整存整取零存整取存本取息教育储蓄定活两便通知存款 对公存款对公账户协议存款 利率 项目准备 &#xff08;贴源层不必写到项目文档&#xff0c;因为没啥操作没啥技术&#xff0c;只是数据。&#xff09; 可以看到&#xff0c;银行的贴源层并不紧…

【两数之和】

两数之和 一、题目二、暴力解法三、哈希表四、map字典1.基本方法.set()添加键值对.get()通过键获取值.has()判断map是否有这个键 2.map和set的联系和区别共同点共同点MapSet 一、题目 二、暴力解法 三、哈希表 解题思路&#xff1a;将nums的元素依次以键值对的方式存储在map字典…

java 线程之间通信-volatile 和 synchronized

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

python学习笔记-08

面向对象基础(OOP)-上 1. 面向对象概述 面向过程&#xff1a;根据业务逻辑从上到下写代码 函数式&#xff1a;将某功能代码封装到函数中&#xff0c;日后便无需重复编写&#xff0c;仅调用函数即可 面向对象(object oriented programming)&#xff1a;将数据与函数绑定到一起…

通过Vue3+高德地图的JS API实现市区地图渲染

效果图1: 核心代码1: <script setup>import { onMounted, onUnmounted } from vue;import AMapLoader from @amap/amap-jsapi-loader;import { message } from ant-design-vue;import school from @/assets/icons/school.svg;import enterprise from @/assets/icons/e…

laravel版本≥ 8.1

laravel10 php ≥ 8.1 且 ≤ 8.3&#xff1f; 8.1 < php < 8.3PHP版本要求在 8.1 到 8.3 之间&#xff0c;包括这两个版本。具体来说&#xff1a;"≥ 8.1" 表示 PHP 的版本至少是 8.1&#xff0c;也就是说 8.1 及以上的版本都可以。 "≤ 8.3" 表示 P…

3dmax怎么渲染又快又清晰?

在3ds Max中&#xff0c;追求快速且清晰的渲染效果是每个设计师的目标。云渲染技术的出现&#xff0c;为这一目标提供了强大的支持。通过云渲染&#xff0c;设计师能够利用远程服务器的强大计算能力&#xff0c;实现快速渲染&#xff0c;同时保持图像的高清晰度。 一、3dmax怎么…

C++ 75 之 异常的传递

#include <iostream> #include <string> using namespace std;// 1.自己准备好一个类&#xff0c;写自己要打印的内容 class MyException{ public:void printE(){cout << "我自己写的异常" << endl;} };class Students02{ public:Students02…

若依 ruoyi 显示隐藏搜索框 显示隐藏列

一、 显示隐藏搜索框 页面搜索关键字 showSearch&#xff0c;设置是否显示 隐藏&#xff1a; 显示&#xff1a; 二、自定义设置 显示隐藏列 1. 页面搜索关键字 right-toolbar&#xff0c;新增&#xff1a; :columns"columns" 2. js下 data(){return{}}中新增&am…

js语法---理解反射Reflect对象和代理Proxy对象

Reflect 基本要点 反射&#xff1a;reflect是一个内置的全局对象&#xff0c;它的作用就是提供了一些对象实例的拦截方法&#xff0c;它的用法和Math对象相似&#xff0c;都只有静态方法和属性&#xff0c;同时reflect也没有构造器&#xff0c;无法通过new运算符构建实例对象&…

WinRAR应用文件图标是白色怎么解决

1.打开程序-选项-设置 2.找到集成-选择全部切换&#xff0c;保存即可。

Mobvista汇量科技解析奥运机会点及营销理念,看广告投放如何抢占先机

四年一度的奥运盛会&#xff0c;作为少有能跨越文化、宗教、种族、行为等各方面差异的体育事件&#xff0c;更能广泛吸引全球观众的目光&#xff0c;成为品牌方和广告主天然的流量磁铁。应用增长平台Mobvista汇量科技为助力各行业开发者、各品牌商家抢占奥运流量&#xff0c;分…

【CT】LeetCode手撕—141. 环形链表

目录 题目1- 思路2- 实现⭐141. 环形链表——题解思路 3- ACM实现 题目 原题连接&#xff1a;141. 环形链表 1- 思路 模式识别 模式1&#xff1a;判断链表的环 ——> 快慢指针 思路 快指针 ——> 走两步慢指针 ——> 走一步判断环&#xff1a;若快慢相遇则有环&a…

北京银行品牌价值提升160亿元首破千亿 位居《中国500最具价值品牌》榜第85位!

6月19日&#xff0c;由世界品牌实验室(World Brand Lab)主办的第二十一届“世界品牌大会”在北京举行&#xff0c;活动现场发布了2024年《中国500最具价值品牌》榜单。在这份基于财务数据、品牌强度和消费者行为分析的年度报告中&#xff0c;北京银行最新品牌价值达1036.62亿元…

将Jar用三种方式生成Windows的安装程序

无论是WEB(spring boot)的JAR,还是JavaFX以及swing的Jar,要生成windows方式。 打包成Windows可执行文件&#xff08;.exe&#xff09;&#xff0c;你可以使用以下三种方法&#xff1a; ### 方法1&#xff1a;使用Inno Setup 1. **构建JavaFX应用程序**&#xff1a; 使用M…

LaTeX 学习 第2节 数学结构

----用教授的方式学习 目录 2.1 上标与下标 2.2 上下画线与花括号 2.3 分式 2.4 根式 2.5 矩阵 ​​​​​​​LaTex安装包&#xff1a;https://download.csdn.net/download/weixin_38135241/89416392 LaTex- windows安装包&#xff1a;https://download.csdn.net/down…

web爬虫笔记:js逆向案例九(某多多 anti_content参数)补环境流程

web爬虫笔记:js逆向案例九(某多多 anti_content参数)补环境流程 一、目标网站:aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS8= 二、接口分析 1、快速定位加密位置(通过搜索/cells/hub/v3快速定位到加密js文件) 2、通过分析可知&#

springBoot多数据源使用、配置

又参加了一个新的项目&#xff0c;虽然是去年做的项目&#xff0c;拿来复用改造&#xff0c;但是也学到了很多。这个项目会用到其他项目的数据&#xff0c;如果调用他们的接口取数据&#xff0c;我还是觉得太麻烦了。打算直接配置多数据源。 然后去另一个数据库系统中取出数据…

王思聪隐形女儿曝光

王思聪"隐形"女儿曝光&#xff01;黄一鸣独自面对怀孕风波&#xff0c;坚持生下爱情结晶近日&#xff0c;娱乐圈掀起了一场惊天波澜&#xff01;前王思聪绯闻女友黄一鸣在接受专访时&#xff0c;大胆揭露了她与王思聪之间的爱恨纠葛&#xff0c;并首度公开承认&#…