【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.mzph.cn/news/855739.shtml

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

相关文章

一键解压,无限可能——BetterZip,您的Mac必备神器!

BetterZip for Mac 是一款高效、智能且安全的解压缩软件&#xff0c;专为Mac用户设计。它提供了直观易用的界面&#xff0c;使用户能够轻松应对各种压缩和解压缩需求。 这款软件不仅支持多种压缩格式&#xff0c;如ZIP、RAR、7Z等&#xff0c;还具备快速解压和压缩文件的能力。…

qt 5.6 qmake手册

qt 5.6 qmake手册 &#xff08;笔者翻译的qmake手册&#xff0c;多数是机翻&#xff0c;欢迎评论区纠错修正&#xff09; Qmake工具有助于简化跨不同平台开发项目的构建过程。它自动生成Makefile&#xff0c;因此创建每个Makefile只需要几行信息。您可以将qmake用于任何软件项目…

32.双击列表启动目标游戏

上一个内容&#xff1a;31.加载配置文件中的游戏到辅助列表 以 31.加载配置文件中的游戏到辅助列表 它的代码为基础进行修改 效果图&#xff1a; 添加列表双击事件 实现代码&#xff1a; LPNMITEMACTIVATE pNMItemActivate reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR…

考研数学强化,880+660正确打开方式

1800题基础做完了&#xff1f;做的怎么样&#xff01; 之所以问你做的怎么样&#xff0c;是因为1800题做的好坏&#xff0c;直接决定了你要不要开始做880题和660题。 有的同学1800题做的很好&#xff0c;做完1800题之后开始做880660没毛病 但是有的同学就是纯纯的为了做题而…

python使用哪种数据库

MySQL 是一个关系型数据库管理系统&#xff0c;由瑞典MySQL AB 公司开发&#xff0c;目前属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一&#xff0c;在 WEB 应用方面&#xff0c;MySQL是最好的 RDBMS (Relational Database Management System&#xff0c;关…

阿里云SSL免费证书部署(nginx)

1.先在阿里云领取免费证书 创建证书 下载证书 得到nginx证书和密钥的压缩包 2.配置nginx 将两个文件放进nginx的opt目录下 先检查有没有ngx_http_ssl_module模块 ngixn -V 如果有进入下一步&#xff0c;没有继续 1.找到你nginx的文件 2.进入添加模块 ./configure --with-h…

git的Cherry pick

Cherry pick Git Cherry Pick详解 https://blog.csdn.net/jam_yin/article/details/131594716 目标: 将开发分支A中提交的部分内容合并到B分支(可能是测试分支) 步骤: vscode安装 点击下图标进入graph

最新版本IntelliJ IDEA安装与“坤活”使用

最新版本IntelliJ IDEA安装与“科学”使用 IntelliJ IDEA安装与坤活下载安装坤活idea1.将下面两个压缩文件解压到安装位置&#xff0c;注意路径不要包含中文空格等特殊符号2.双击 install-all-users.vbs &#xff0c;然后点击确定&#xff0c;等到出现 Done的弹窗3. 打开idea复…

远程桌面另一台服务器连接不上,局域网IP如何访问另一台服务器

在IT运维工作中&#xff0c;远程桌面连接是日常工作中不可或缺的一部分。然而&#xff0c;当尝试远程桌面连接至另一台服务器时&#xff0c;如果连接不上&#xff0c;可能会引发一系列问题&#xff0c;影响到工作效率和信息安全。特别是在局域网环境中&#xff0c;确保能够正确…

AI口语练习APP的技术难点

AI口语练习APP旨在帮助用户练习口语&#xff0c;因此其核心功能是语音识别和语音评测。以下是一些AI口语练习APP的主要技术难点。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1. 语音识别 语音识别是将语音信号转换为文本的过程。…

【golang学习之旅】使用VScode安装配置Go开发环境

1. 下载并安装Go1.1 下载地址1.2 选择版本并下载1.3 安装目录1.4 验证是否安装成功 2. 配置环境变量2.1 配置步骤2.2 GO部分环境变量说明 3. 下载或更新 Vscode3.1 下载地址3.2 安装步骤 4. 为Go开发配置VScode 1. 下载并安装Go 1.1 下载地址 https://studygolang.com/dl 1.…

ChinaTravel成流量密码,景区如何打造视频监控管理平台提升旅游体验

随着中国经济的飞速发展和人民生活水平的持续提高&#xff0c;旅游已经成为越来越多人休闲放松的首选方式。近期&#xff0c;随着互联网的普及和社交媒体的兴起&#xff0c;以及免签政策带火入境游&#xff0c;“ChinaTravel”已成为社交网络上的一大流量密码&#xff0c;吸引了…

送检了200多款主食冻干,花费百万后,我测评了VE、希喂、朗诺!

主食冻干真是养猫的好帮手&#xff0c;方便、易喂&#xff0c;还能为猫咪提供全面营养&#xff01;它模拟了猫咪的自然捕猎饮食&#xff0c;采用低温脱水和灭菌技术制成。这样的主食冻干不仅让铲屎官们的喂养变得轻松&#xff0c;还满足了猫咪的味蕾和营养需求。更重要的是&…

跟《经济学人》学英文:2024年6月15日这期 Chinese electric vehicles (EVs)

The EU hits China’s carmakers with hefty new tariffs Duties will only hold them back for a while 欧盟对中国汽车制造商征收高额新关税 hit: 对xxx施加 在句子"The EU hits China’s carmakers with hefty new tariffs"中&#xff0c;“hits”的意思是“对…

Fastjson 结合 jdk 原生反序列化的利用手法 ( Aliyun CTF )

2023 Aliyun CTF ezbean是一道CTF java反序列化题目。 题目的目的是让选手通过一个java原生反序列化入口&#xff0c;最终达成RCE。本文对题目的几种解法做了具体的分析&#xff0c;主要分为预期解法和非预期解法两种思路。通过对Fastjson在反序列化的行为分析&#xff0c;从两…

华为---OSPF单区域配置(一)

09、OSPF 9.1 OSPF单区域配置 9.1.1 原理概述 为了弥补距离矢量路由协议的不足&#xff0c;IETF组织开发了一种基于链路状态的内部网关协议——OSPF&#xff08;Open Shortest Path First&#xff0c;开放式最短路径优先&#xff09;。 OSPF作为基于链路状态的协议&#xf…

【网络安全学习】漏洞扫描:- 02- nmap漏洞扫描

1.nmap的介绍 Nmap是一款功能强大的网络探测和安全扫描工具&#xff0c;可以对目标进行端口扫描、服务探测、操作系统指纹识别等操作。 Nmap自带了许多内置的NSE脚本&#xff0c;它们可以根据不同的目标和场景来执行不同的功能。这些脚本存放在Nmap安装目录**/usr/share/nmap…

Fastjson 反序列化漏洞(CVE-2017-18349)

目录 Fastjon介绍 序列化与反序列化 漏洞产生的原因 漏洞点测试 使用dnslog探测 反弹shell 这一篇是学习JAVA里面的fastjson 反序列化漏洞&#xff0c;这里还是参考别的师傅总结的文章进行学习 Fastjon介绍 Fastjson 是一个 Java 库&#xff0c;可以将 Java 对象转换为 …

美业人专用宝藏系统、Java收银系统源码分享-美业SAAS系统的应用价值分析

美业SAAS系统&#xff08;Software as a Service&#xff09;在美容、美发、美甲等行业中具有重要的应用价值。这种系统为美业提供了一种数字化解决方案&#xff0c;帮助企业更高效地管理业务和客户关系。 以下是博弈美业SAAS系统的应用价值分析&#xff1a; 1.经营管理&#…

西门子PLC1200--与电脑连接的TCP通讯

本例演示将PLC作为服务器&#xff0c;电脑作为客户端&#xff0c;进行通讯。 反过来也是一回事&#xff0c;就不啰嗦了。 二者进行TCP通讯&#xff0c;是基于网线的&#xff0c;电脑上用的是网口&#xff0c;PLC上用的是Profinet接口&#xff0c;即PN口。 软件工具准备 西门子…