[C++随想录] 二叉搜索树

搜素二叉树

  • 二叉搜索树的使用
  • 二叉搜索树的模拟实现(K)
    • 整体结构
    • 循环版本
    • 递归版本
  • 二叉搜索树的应用
  • 源码(kv)

二叉搜索树的使用

二叉搜索树 相较于 普通的二叉树来说:

  1. 根节点的左子树的所有键值都 小于 根节点, 根节点的右子树的所有键值 大于 根节点
  2. 根节点的 左右子树 都是 二叉搜索树
  3. 中序遍历升序的 ⇒ 二叉搜素树 又叫作 二叉排序树
  • 子树 && 节点
  1. 查找
    假如查找 key, 有如下 四种情况:
    1. 如果 key > 根节点的值, 那么就去根节点的右子树去查找
    2. 如果 key <根节点的值, 那么就去根节点的左子树去查找
    3. 如果 key = 根节点的值, 那么就找到了
    4. 如果找到 , 那就不存在
  • 查找的时间复杂度是 O(高度次), 而不是 O(logN)
    如果是 完全二叉树, 那么就是 O(logN); 如果 退化到极限情况, 类似于链表, 那么就是 O(N)

    所以, 总结下来, 时间复杂度就是 O(高度次)
    那么如何解决这种 退化问题呢? ⇒ AVL树 和 红黑树 就是针对这种情况做了特殊处理 --> 旋转
  1. 插入
    总体思想: 找到非空节点去插入

  2. 删除key

    1. 先找到key的位置, 有两种情况:
      1. 没找到, 那就直接返回
      2. 找到了key的位置, 记作cur. 找到了也有三种情况:
        1. cur的左子树为空
        2. cur的右子树为空
        3. cur的左右子树都不为空

由于 cur要进行删除, 要把cur后面的内容链接到parent的后面. && cur也有两种可能 parent的左子树 or 右子树我们要cur后面的内容链接到 cur处于parent的位置
删除具体如下👇👇👇

  1. cur的右子树为空
    (1) cur是parent的左子树

    (2) cur是parent的右子树
  2. cur的左子树为空
    (1) cur是parent的左子树

    (2) cur是parent的右子树
  3. cur的左右子树都不为空
    🗨️删除掉cur, 那么我们如何链接cur的左右子树呢?
    • 可以找一个节点来替换掉cur, 然后我们来处理这个节点的链接关系就好了
      🗨️替换过去, 也不改变二叉搜索树的结构, 那么节点是什么好呢? 后面集中处理这个节点, 那么这个节点应该容易处理才对, 那么这个节点是叶子节点吗?
    • 替换过去, 不改变二叉树的结构 — — 替换节点应该为 cur的左子树的最大节点 或者 cur的右子树的最小节点中序遍历, cur旁边的两个数; 中序是 左跟右, ⇒ 那么就应该是左子树的最大节点, 或者右子树的最小节点
      左子树的最大节点, 或者右子树的最小节点; 正好是叶子节点 ⇒ 那么我们处理这个替换节点也比较 容易 ⇒ 思想同上 替换节点的左子树为空, 或 替换节点的右子树为空

二叉搜索树的模拟实现(K)

整体结构


Node类

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

BSTree类

template<class T>
class BSTree
{typedef BSTreeNode<T> Node;
public:BSTree():_root(nullptr){}// 析构函数~BSTree(){_BSTree(_root);}private:// 析构函数void _BSTree(Node* root){if (root == nullptr)return;// 后序遍历进行删除_BSTree(root->_left);_BSTree(root->_right);delete root;}// 成员函数Node* _root;
}

循环版本

  1. find
Node* find(const K& key)
{return _find(_root, key);
}private:
Node* _find(Node* root, const T& key)
{Node* cur = root;while (cur){if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;
}
  1. insert
bool insert(const T& key)
{Node* newnode = new Node(key);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* cur = _root;// 寻找插入的位置while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{break;}}// 链接if (key > parent->_key){parent->_right = newnode;}else if (key < parent->_key){parent->_left = newnode;}else{return false;}return true;
}
  1. inorder
void Inorder()
{_Inorder(_root);
}private:
void _Inorder(Node* root)
{if (root == nullptr)return;_Inorder(root->_left);std::cout << root->_key << " ";_Inorder(root->_right);}
  1. erase

bool erase(const T& key)
{return _erase(_root, key);
}private:
bool _erase(Node* root, const T& key)
{// 先找到位置Node* parent = root;Node* cur = root;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}	else if (key < cur->_key){parent = cur;cur = cur->_left;}// 找到了else{// 左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{// 判读cur是parent的位置if (cur == parent->_left){parent->_left = cur->_right;}else if (cur == parent->_right){parent->_right = cur->_right;}}}// 右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{// 判读cur是parent的位置if (cur == parent->_left){parent->_left = cur->_left;}else if (cur == parent->_right){parent->_right = cur->_left;}}}// 左右都不为空else{// 先找到cur的左子树的最大值 或 右子树的最小值// parent必须初始化为cur -- 以防删除的就是头节点Node* parent = cur;Node* LeftMax = cur->_left;while (LeftMax->_right){parent = LeftMax;LeftMax = LeftMax->_right;}// 交换cur 和 LeftMax的值std::swap(cur->_key, LeftMax->_key);// 改变链接关系if (parent->_left == LeftMax){parent->_left = LeftMax->_left;}else if (parent->_right == LeftMax){parent->_right = LeftMax->_left;}cur = LeftMax;}// 集中释放 curdelete cur;return true;}}return false;
}

递归版本

  1. findr
    无需链接关系 — — 不用引用即可
    1. 递归退出条件 root == nullptr, 那就返回nullptr
    2. 根据二叉搜索数的特性: 大了往右边走, 小了往左边走, 相等就返回当前节点的指针;
Node* findr(const T& key)
{return _findr(_root, key);
}private:
Node*_findr(Node* root, const T& key)
{if (root == nullptr)return nullptr;if (key < root->_key){_findr(root->_left, key);}else if (key > root->_key){_findr(root->_right, key);}else{return root;}
}
  1. insertr
    需要重新链接 -- -- 引用的妙用
    总体思想 : 遇到空就插入
    1. 递归返回条件 : 遇到空, 插入后, 返回true
    2. 二叉树的特性: 大了往右边走, 小了往左边走, 相等返回false

bool insertr(const T& key)
{return _insertr(_root, key);
}private:
bool _insertr(Node*& root, const T& 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;}
}
  1. eraser
    需要重新链接 -- -- 引用的妙用
    1. 递归结束条件: 遇到空就返回 false
    2. 先找到位置, 记作 cur
    3. cur有三种情况 :cur的左子树为空, cur的右子树为空, cur的左右子树都不为空; 三种情况分类讨论

这个和上面的 引用的妙用是一样的道理, 那么我就不在这里画 递归展开图

bool eraser(const T& key)
{return _eraser(_root, key);
}private:
bool _eraser(Node*& root, const T& key)
{if (root == nullptr){return false;}if (key > root->_key){_eraser(root->_right, key);}else if (key < root->_key){_eraser(root->_left, key);}else{// 由于是上面节点的引用 && 要删掉root节点// ⇒ 找一个背锅侠来代替root节点去删除Node* tem = root;// 左子树为空if (root->_left == nullptr){root = root->_right;}//右子树为空else if (root->_right == nullptr){root = root->_left;}// 左右子树都不为空else{// 找到左树的最大节点Node* maxleft = root->_left;while (maxleft->_right){maxleft = maxleft->_right;}// 交换root 和 maxleft的值std::swap(maxleft->_key, root->_key);// 重新链接root = maxleft->_left;// 背锅侠就位tem = maxleft;}// 统一删除delete tem;return true;}return false;
}

二叉搜索树的应用

二叉搜索树主要有两个版本 K版本 和 KV版本
KV版本 相较于 K版本 就多了个 value

template<class K, class V>struct BSTreeNode{public:BSTreeNode(const K& key, const V& value):_left(nullptr),_right(nullptr),_key(key),_value(value){}public:BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;};
template<class K, class V>
class BSTree
{typedef BSTreeNode<K, V> Node;
public:BSTree():_root(nullptr){}
private:Node* _root;
}

由于 还是对 K 进行操作 ⇒ 我们这里就不写 KV的代码了. 后面源码会附上 KV的完整代码


二叉搜索树主要应用于两种模型: K模型 和 KV模型

  1. K模型 — — 根据关键码Key去解决 在不在的问题
    比如 : 判断单词是否拼写错误 (将词库导入二叉搜索树, 然后判断在不在)
void test1()
{// 模拟导入词库muyu::BSTree<string, string> World;World.insert("insert", "插入");World.insert("input", "输入");World.insert("output", "输出");World.insert("love", "爱情");string str;while (cin >> str){// 查找是否在词库中出现auto ret = World.find(str);if (ret){cout << "输入正确" << endl;}else{cout << "查无单词, 请重新输入" << endl;}}
}int main()
{test1();return 0;
}

运行结果:

  1. KV模型 — — 每一个关键码Key, 都有一个与之对应的 Value, 存在 <Key, Value>键值对
    比如: 统计水果出现的次数
void test2()
{muyu::BSTree<string, int> cnt;string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };for (const auto& e : arr){auto res = cnt.find(e);// 第一次插入, 次数就给个1if (!res){cnt.insert(e, 1);}// 不是第一次插入, 就在key对应的value进行++else{res->_value++;}}cnt.Inorder();
}int main()
{test2();return 0;
}

运行结果:

苹果 6
西瓜 3
香蕉 2

源码(kv)

#pragma oncenamespace muyu
{template<class K, class V>struct BSTreeNode{public:BSTreeNode(const K& key = K(), const V& value = V()):_left(nullptr),_right(nullptr),_key(key),_value(value){}public:BSTreeNode<K,V>* _left;BSTreeNode<K,V>* _right;K _key;V _value;};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){}~BSTree(){_BSTree(_root);}bool insert(const K& key, const V& value){Node* newnode = new Node(key, value);if (_root == nullptr){_root = newnode;return true;}Node* parent = nullptr;Node* cur = _root;// 寻找插入的位置while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{break;}}// 链接if (key > parent->_key){parent->_right = newnode;}else if (key < parent->_key){parent->_left = newnode;}else{return false;}return true;}bool insertr(const K& key){return _insertr(_root, key);}void Inorder(){_Inorder(_root);}Node* find(const K& key){return _find(_root, key);}Node* findr(const K& key){return _findr(_root, key);}bool erase(const K& key){return _erase(_root, key);}bool eraser(const K& key){return _eraser(_root, key);}private:void _BSTree(Node* root){if (root == nullptr)return;// 后序遍历进行删除_BSTree(root->_left);_BSTree(root->_right);delete root;}void _Inorder(Node* root){if (root == nullptr)return;_Inorder(root->_left);std::cout << root->_key << " " << root->_value << std::endl;_Inorder(root->_right);}Node* _insertr(Node*& root, const K& key, const V& value){if (root == nullptr){root = new Node(key, value);return root;}if (key > root->_key){return _insertr(root->_right, key);}else if (key < root->_key){return _insertr(root->_left, key);}else{return nullptr;}}Node* _find(Node* root, const K& key){Node* cur = root;while (cur){if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}Node* _findr(Node* root, const K& key){if (root == nullptr)return nullptr;if (key < root->_key){_findr(root->_left, key);}else if (key > root->_key){_findr(root->_right, key);}else{return root;}}bool _erase(Node* root, const K& key){// 先找到位置Node* parent = root;Node* cur = root;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}	else if (key < cur->_key){parent = cur;cur = cur->_left;}// 找到了else{// 左为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{// 判读cur是parent的位置if (cur == parent->_left){parent->_left = cur->_right;}else if (cur == parent->_right){parent->_right = cur->_right;}}}// 右为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{// 判读cur是parent的位置if (cur == parent->_left){parent->_left = cur->_left;}else if (cur == parent->_right){parent->_right = cur->_left;}}}// 左右都不为空else{// 先找到cur的左子树的最大值 或 右子树的最小值Node* parent = cur;Node* LeftMax = cur->_left;while (LeftMax->_right){parent = LeftMax;LeftMax = LeftMax->_right;}// 交换cur 和 LeftMax的值std::swap(cur->_key, LeftMax->_key);// 改变链接关系if (parent->_left == LeftMax){parent->_left = LeftMax->_left;}else if (parent->_right == LeftMax){parent->_right = LeftMax->_left;}cur = LeftMax;}delete cur;return true;}}return false;}bool _eraser(Node*& root, const K& key){if (root == nullptr){return false;}if (key > root->_key){_eraser(root->_right, key);}else if (key < root->_key){_eraser(root->_left, key);}else{Node* tem = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{Node* maxleft = root->_left;while (maxleft->_right){maxleft = maxleft->_right;}std::swap(maxleft->_key, root->_key);root = maxleft->_left;tem = maxleft;}delete tem;return true;}return false;}Node* _root;};
}

晚日寒鸦一片愁。柳塘新绿却温柔。若教眼底无离恨,不信人间有白头。
肠已断,泪难收。相思重上小红楼。情知已被山遮断,频倚阑干不自由。
— — 辛弃疾· 《鹧鸪天》

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

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

相关文章

2023年中国石英矿资源现状及行业市场供需分析[图]

石英矿为常见的非金属矿物质&#xff0c;具有储量大、分布广、易开采等特点&#xff0c;根据不同成矿特性和理化特性&#xff0c;石英矿物可分为岩浆岩型、变质型、热液型、沉积型&#xff0c;对应的石英岩分别为花岗伟晶岩、脉石英岩、石英岩和石英砂岩。 石英矿物类型和特点…

无人机遥控中应用的2.4GHz无线芯片

无人驾驶飞机简称“无人机”&#xff0c;英文缩写为“UAV”&#xff0c;是利用无线电遥控设备和自备的程序控制装置操纵的不载人飞机&#xff0c;或者由车载计算机完全地或间歇地自主地操作。是一种不需要人操控就能够自主飞行的飞行器&#xff0c;它可以执行多种任务&#xff…

大数据开发中的秘密武器:探索Hadoop纠删码的奇妙世界

随着大数据技术的发展&#xff0c;HDFS作为Hadoop的核心模块之一得到了广泛的应用。为了系统的可靠性&#xff0c;HDFS通过复制来实现这种机制。但在HDFS中每一份数据都有两个副本&#xff0c;这也使得存储利用率仅为1/3&#xff0c;每TB数据都需要占用3TB的存储空间。因此&…

【网络安全入门】学习网络安全必须知道的100 个网络基础知识

前言 先领取资料再阅读哦 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包&#xff08;附面试题答案&#xff09;&#xff0c;免费分享&#xff01; 【282G】网络安全&黑客技术零基础到进阶全套学习大礼包&#xff08;附面试题答案&#xff09;&#xff0c;免…

安装Docker

本安装教程参考Docker官方文档&#xff0c;地址如下&#xff1a;https://docs.docker.com/engine/install/centos/ 卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \ docker-client \ docker-client-latest \ docker-common…

【计算机网络】TCP 协议的相关特性

TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的协议。以下是TCP协议的相关特性&#xff1a; 可靠性&#xff1a;TCP通过确认和重传机制保证数据的可靠传输。 面向连接&#xff1a;TCP在传输数据前需要先建立连接。连接的建立过程包括三次握手…

C++ 读MTK代码 综测校准 PSU读开关电源电压或电流 visa

1.C定义dll接口 // The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the PSU_DLL_EXPORTS // symbol defined on the command line. this symbol should not b…

Zookeeper 和 Kafka 工作原理及如何搭建 Zookeeper集群 + Kafka集群

目录 1 Zookeeper 1.1 Zookeeper 定义 1.2 Zookeeper 工作机制 1.3 Zookeeper 特点 1.4 Zookeeper 数据结构 1.5 Zookeeper 应用场景 1.6 Zookeeper 选举机制 2 部署 Zookeeper 集群 2.1 安装前准备 2.2 安装 Zookeeper 3 Kafka 3.1 为什么需要消息队列&#xff08;…

防雷检测的项目和行业的等级区分

防雷检测是指对雷电防护装置的性能、质量和安全进行检测的活动&#xff0c;是保障人民生命财产和公共安全的重要措施。 地凯科技防雷检测的项目内容包括接闪器检测、引下线检测、接地装置检测、防雷区的划分、电磁屏蔽防雷检测、等电位连接检测、及电涌保护器 (SPD)性能检测。…

C#,数值计算——分类与推理Phylo_nj的计算方法与源程序

1 文本格式 using System; using System.Collections.Generic; namespace Legalsoft.Truffer { public class Phylo_nj : Phylagglom { public double[] u; public override void premin(double[,] d, int[] nextp) { i…

电商接口中API key 和 token 有什么区别?

API key 和 token 就有这种问题&#xff0c;它们都是作为一种身份验证机制。前几天我在一次讨论中&#xff0c;有人提到这两个词可以互换使用。大约两分钟后&#xff0c;我不得不停止谈话并说“你们应该知道它们是不同的&#xff0c;对吧&#xff1f;”‍&#xff0c;说完会上鸦…

idea的debug调试

目录 断点条件设置(condition) 断点表达式(evaluate expression) 断点回退(reset frame) 断点条件设置(condition) 条件断点&#xff0c;一般是满足我们设置的某个条件时&#xff0c;debug断点才会生效。这种条件断点设置&#xff0c;我们一般用在多重循环中。 这儿我们以li…

前端数据可视化之【series、series饼图配置】配置项

目录 &#x1f31f;Echarts配置项&#x1f31f;series&#x1f31f;饼图 type:pie&#x1f31f;写在最后 &#x1f31f;Echarts配置项 ECharts开源来自百度商业前端数据可视化团队&#xff0c;基于html5 Canvas&#xff0c;是一个纯Javascript图表库&#xff0c;提供直观&…

预测宝可梦武力值、分类宝可梦

regression case 股票预测 无人车看到的各种sensor 影像镜头看到马路上的东西作为输入&#xff0c;输出就是方向盘角度等等的操纵策略 scalar 标量 这个是热力图&#xff0c;相当于你的XYZ但是Z用颜色表示了 closed-form solution 闭合解 learning rate事先定好的数值 在lin…

Inbound marketing | LTD入站营销是对Hubspot集客营销的升级

你如何理解Inbound marketing&#xff1f; 你如何理解Inbound marketing。 集客营销抑或是入站营销。 2006年&#xff0c;MIT的在校学生BrianHalligan和DharmeshShah&#xff08;hubspot的创始人&#xff09;首次提出Inbound marketing&#xff0c;有别于推广式营销的一种全…

Ethernet Protocol

以太网协议说明 1 以太网子层架构 1)MAC and MAC CONTROL Sublayer MAC 负责以太网数据格式中所述的以太网成帧协议以及这些帧的错误检测。MAC 独立于并可以连接到任何类型的物理层设备。这提供了 MAC 子层的实时流控制操作。 MAC CONTROL 和 MAC 子层均由内核在所有操作模式…

layui中页面切分

1.引入Split插件 2.切屏比例设置 pallet与material为标签的id 3.html内部标签上设置切分盒子 4参考网站 : 网站链接

PS001:PS2020及GeographicImager6.2安装

引言&#xff1a;Geographic ImagerV6.2是一款专业的PS地理成像插件&#xff0c;通过安装这款插件可实现在PS中加载4G以上的.bigtiff格式影像并对其进行修改与保存。并且这款软件拥有投影信息修改、基于地理坐标进行影像裁切等多种功能。 一、插件介绍 Geographic ImagerV6.2是…

如何正确维护实验室超声波清洗机

实验室一直被视作一个严谨且严肃的场所&#xff0c;在其中所做的试验都需要遵照一定流程&#xff0c;所用的设备也经过了细致化挑选&#xff0c;例如实验室超声波清洗机&#xff0c;其性能远强于普通类别的清洗机。专门负责采购的实验室人员&#xff0c;通常会对质量优服务好的…

02.MySQL函数及约束、多表笔记

函数 函数是指一段可以直接被另一段程序调用的程序或代码。 字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的几个如下&#xff1a; 函数功能CONCAT(S1,S2,…Sn)字符串拼接&#xff0c;将S1,S2,.Sn拼接成一个字符串LOWER(str)将字符串str全部转为小写UPPER(str)将…