【数据结构】搜索二叉树

二叉搜索树

二叉树的博客

在之前的数据结构的文章中已经基本对二叉树有一定的了解,二叉搜索树也是一种数据结构,下面将对二叉搜索树进行讲解。

二叉搜索树的概念

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

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

二叉搜索树的特点是搜索数据比较快,最多高度次就可以找到所值,其高度最大就是O(N)

二叉搜索树的实现过程

基本框架

  • 需要有一个struct的类(struct的类默认公开)来包含一个节点的所有特性,包括其可以指向左子树、右子树以及其包含的数据。
  • 然后使用class的类来对这棵二叉搜索树进行封装。
template<class K>
struct BSTreeNode
{BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;
};template<class K>
struct BSTree
{typedef BSTreeNode<K> Node;
public:private:Node* _root;
};

初始化二叉树:

	//初始化节点BSTree():_root(nullptr){}

二叉搜索树的插入

插入过程:

  • 当树为空的时候,直接新增节点,赋值给root指针。
  • 树不为空,按二叉搜索树的性质查找插入位置,插入新节点。
//插入数据
bool insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (cur->_key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;
}

搜索二叉树的打印(使用中序遍历)

        在这段代码中,使用_root作为参数传递给_InOrder函数,而不是直接在_InOrder函数中使用__root,主要是为了增加代码的灵活性和可复用性。

这样做的好处是,_InOrder函数可以处理不同的二叉树,而不仅仅局限于某个特定的二叉树对象。通过将二叉树的根节点作为参数传递给_InOrder函数,就可以对任意给定的二叉树进行中序遍历。

如果直接在_InOrder函数中使用_root,那么_InOrder函数就只能操作类内部固定的_root成员变量所代表的二叉树。而通过参数传递的方式,可以在需要的时候将不同的二叉树根节点传递给_InOrder函数,使其能够对各种不同的二叉树进行操作,提高了函数的通用性。

	//二叉树的升序打印void InOrder(){_InOrder(_root);cout << endl;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

二查搜索树的查找

  1. 从根开始查找,如果比根小走左路,比根大走右路。
  2. 最多查找高度次,如果没找到则不存在。

二叉搜索树的删除(难点)

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

  1. 要删除的节点无孩子节点。
  2. 要删除的节点只有左孩子节点。
  3. 要删除的节点只有右孩子节点。
  4. 要删除的节点有左、右孩子节点。

总结下来,实际中真正要删除的情况只有三种:

  1.  删除该节点且使被删除节点的双亲结点指向被删除节点的左孩子节点——直接删除。
  2. 删除该节点且使被删除节点的双亲结点指向被删除节点的右孩子节点——直接删除。
  3. 在它的右子树中寻找中序下的第一个节点(数值最小)或者在它的左子树中寻找中序前的最后一个节点(数值最大),用它的值填补到被删除节点中,再来处理该节点的删除问题【替换法】。 

再进行缩减就是:

  1. 没有孩子或者只有一个孩子,进行托孤。
  2. 有俩个孩子进行替换。

	//搜索二叉树的删除bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if(cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{//替换法Node* LeftMax = _root->_left;Node* parent = _root;while (LeftMax->_right){parent = LeftMax;LeftMax = LeftMax->_right;}swap(LeftMax->_key, cur->_key);if (parent->_left == LeftMax){parent->_left = LeftMax->_left;}else{parent->_right = LeftMax->_left;}cur = LeftMax;}delete cur;return true;}}return false;}

分析该代码:

二叉树的递归实现

在递归实现的过程中,唯一需要注意的地方是使用了指针引用,这是因为引用不能改变指向,但是在递归函数的过程中都会重新定义引用。

递归升序打印

	//二叉树的升序打印void InOrder(){_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}

递归查找

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

递归插入

	//插入bool InsertR(const K& key){return _InsertR(_root, key);}
private:bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key > key){return _InsertR(root->_left, key);}else if (root->_key < key){return _InsertR(root->_right, key);}else{return false;}}

递归删除

	//删除bool EraseR(const K& key){return _EraseR(_root, key);}
private:bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key > key){return _EraseR(root->_left, key);}else if(root->_key < key){return _EraseR(root->_right, key);}else{Node* del = 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;}swap(root->_key, MaxLeft->_key);return _EraseR(root->_left, key);}delete del;return true;}}

二叉树完整代码展示

#pragma once
#include<iostream>
using namespace std;template<class K>
struct BSTreeNode
{BSTreeNode(const K& key):_left(nullptr),_right(nullptr),_key(key){}BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;
};template<class K>
struct BSTree
{typedef BSTreeNode<K> Node;
public://初始化节点BSTree():_root(nullptr){}//插入数据bool insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (cur->_key > parent->_key){parent->_right = cur;}else{parent->_left = cur;}return true;}//搜索二叉树的查找bool Find(const K& key){Node* cur = _root;while (cur){if (cur->_key > key){cur = cur->_left;}else if (cur->_key < key){cur = cur->_right;}else{return true;}}return false;}//搜索二叉树的删除bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_key > key){parent = cur;cur = cur->_left;}else if (cur->_key < key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}}else if(cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}}else{//替换法Node* LeftMax = _root->_left;Node* parent = _root;while (LeftMax->_right){parent = LeftMax;LeftMax = LeftMax->_right;}swap(LeftMax->_key, cur->_key);if (parent->_left == LeftMax){parent->_left = LeftMax->_left;}else{parent->_right = LeftMax->_left;}cur = LeftMax;}delete cur;return true;}}return false;}//二叉树的升序打印void InOrder(){_InOrder(_root);cout << endl;}//查找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);}
private:bool _EraseR(Node*& root, const K& key){if (root == nullptr){return false;}if (root->_key > key){return _EraseR(root->_left, key);}else if(root->_key < key){return _EraseR(root->_right, key);}else{Node* del = 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;}swap(root->_key, MaxLeft->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (root->_key > key){return _InsertR(root->_left, key);}else if (root->_key < key){return _InsertR(root->_right, key);}else{return false;}}bool _FindR(Node* root, const K& key){if (root == nullptr){return false;}if (root->_key > key){return _FindR(root->_left, key);}else if (root->_key < key){return _FindR(root->_right, key);}else{return true;}}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}Node* _root;
};

二叉树的应用

1.K模型:K模型即只有key作为关键码,结构中只需要存储key即可,关键码即为需要搜索到的值。K模型可以快速判断在不在的场景,之前模拟实现的就是K模型。

  • 应用1:门禁系统。
  • 应用2:小区车辆出入系统(是否允许进入)。
  • 应用3:判断单词是否正确。

2.KV模型:每一个关键码key,都会对应一个value的值,即< key,value >。KV模型可以通过一个值快速找到另外一个值。

  • 应用1:手机号码查询快递。
  • 应用2:商城车辆出入系统(记录实际)。
  • 应用3:高铁实名制车票系统。
  • 应用4:英汉词典的中英文对应关系。

KV模型代码展示:

#include<iostream>using namespace std;namespace key_value
{template<class K, class V>struct BSTreeNode{BSTreeNode<K, V>* _left;BSTreeNode<K, V>* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key), _value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:BSTree():_root(nullptr){}void InOrder(){_InOrder(_root);cout << endl;}Node* FindR(const K& key){return _FindR(_root, key);}bool InsertR(const K& key, const V& value){return _InsertR(_root, key, value);}bool EraseR(const K& key){return _EraseR(_root, key);}private:bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else{Node* del = root;// 1、左为空// 2、右为空// 3、左右都不为空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;}swap(root->_key, leftMax->_key);return _EraseR(root->_left, key);}delete del;return true;}}bool _InsertR(Node*& root, const K& key, const V& value){if (root == nullptr){root = new Node(key, value);return true;}if (root->_key < key){return _InsertR(root->_right, key, value);}else if (root->_key > key){return _InsertR(root->_left, key, value);}else{return false;}}Node* _FindR(Node* root, const K& key){if (root == nullptr)return nullptr;if (root->_key < key){return _FindR(root->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return root;}}void _InOrder(Node* root){if (root == NULL){return;}_InOrder(root->_left);cout << root->_key << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root;};void TestBSTree1(){//BSTree<string, Date> carTree;BSTree<string, string> dict;dict.InsertR("insert", "插入");dict.InsertR("sort", "排序");dict.InsertR("right", "右边");dict.InsertR("date", "日期");string str;while (cin >> str){BSTreeNode<string, string>* ret = dict.FindR(str);if (ret){cout << ret->_value << endl;}else{cout << "无此单词" << endl;}}}void TestBSTree2(){// 统计水果出现的次数string arr[] = { "西瓜", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };BSTree<string, int> countTree;for (auto& str : arr){auto ret = countTree.FindR(str);if (ret == nullptr){countTree.InsertR(str, 1);}else{ret->_value++;}}countTree.InOrder();}
}

二叉树的性能分析

插入和删除都必须先查找,查找效率代表了二叉搜索树的各个操作的性能。

        对于n个节点的二叉搜索树,若是每一个元素查找的概率相等,则二叉搜索树平均查找长度是节点在二叉搜索树的深度的函数,即节点越深,则比较的次数越多。

        但是如果同一组数据的插入次序不同,可能得到不同结构的二叉搜索树。

最优的情况是:二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:logN

最差的情况是:二叉搜索树退化成单支树(或者类似单支),其平均的比较次数为N。

        使用有序数组进行二分查找的时候,其缺点是插入与删除效率不高;

        使用搜索二叉树进行二分查找的时候,其可以很好地利用其特性进行查找、插入、删除、排序等操作,但是搜索二叉树的唯一缺点就是下限无保障(一条光杆树)。

        所以在后续的C++文章中会介绍AVL树、红黑树、B树来解决这个问题。

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

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

相关文章

ObservableCollection新增数据前判断数据是否存在

public class MyDataModel {public int Id { get; set; }public string Name { get; set; }}public static void Main(){// 创建 ObservableCollectionObservableCollection<MyDataModel> myDataCollection new ObservableCollection<MyDataModel>{new MyDataMode…

[运维]解决Docker拉取镜像失败问题

问题描述 Docker 拉取镜像失败&#xff0c;报错如下&#xff1a; error pulling image configuration: download failed after attempts6: read tcp 192.168.10.2:55372->104.16.99.215:443: read: connection reset by peer一开始是xxx: Pulling fs layer&#xff0c;随后…

外贸SOHO如何选择企业邮箱

外贸SOHO&#xff08;Small Office Home Office&#xff09;企业正以前所未有的速度崛起&#xff0c;然而&#xff0c;要在这片竞争激烈的蓝海中立足&#xff0c;高效的全球通信能力、坚实的安全防线、成本效益的考量以及专业的品牌形象塑造&#xff0c;缺一不可。本文旨在为外…

Python图形编程-在PyGame中使用OpenGL

在PyGame中使用OpenGL 文章目录 在PyGame中使用OpenGL1、使用PyGame初始化项目2、绘制物体3、迭代动画4、利用变换矩阵5、多重转换执行6、完整示例代码PyOpenGL是Python和OpenGL API之间的标准库,而PyGame是用于在Python中制作游戏的标准库。它提供了内置的图形和音频库,在本…

webStorm 实时模板笔记

文章目录 1、单斜杠效果 2、双斜杠效果 3、控制台打印效果 1、单斜杠 /** $END$ */效果 2、双斜杠 /*** $END$* author Ikun* since $DATE$ $TIME$ */DATE date() ✔ TIME time() ✔效果 3、控制台打印 console.log("███████$EXPR_COPY$>>>>&a…

前端调试合集(包含移动端/内嵌h5)

代码内使用方法 alert/console alert和console.log作为JS最基本的调试能力&#xff0c;提供了简易版的断点 (只能断一下) 和输出 (只能输出字符串) 能力&#xff0c;可以在代码运行到预期的位置输出预期的log&#xff0c;通过对不同流程下写入alert&#xff0c;输出变量的值来…

Java笔试分享

1、设计模式&#xff08;写>3种常用的设计模式&#xff09; 设计模式是在软件工程中解决常见问题的经验性解决方案。以下是一些常用的设计模式&#xff1a; 单例模式&#xff08;Singleton&#xff09;&#xff1a; 意图&#xff1a;确保一个类只有一个实例&#xff0c;并…

从0到1,AI我来了- (1)从AI手写数字识别开始

前两篇我们我们把控制台、Python环境Anaconda 搞定了&#xff0c;接下来&#xff0c;我们快速进入主题&#xff0c;把AI 界的“Hello World” 实现一下&#xff0c;有个感觉&#xff0c;再逐步了解一些AI的概念。 1、Pytorch 安装 1) 什么是Pytorch? 一个深度学习框架&#…

通信原理-实验六:实验测验

实验六 实验测验 一&#xff1a;测验内容和要求 测试需要完成以下几个步骤&#xff1a; 配置好以下网络图&#xff1b;占总分10%&#xff08;缺少一个扣一分&#xff09;根据下面图配置好对应的IP和网关以及路由等相关配置&#xff0c;保证设备之间连通正常&#xff1b;占总…

深入探讨 Java 语言的基本数据类型、字符串与数组

Java 语言自1995年推出以来&#xff0c;凭借其坚实的面向对象编程基础和跨平台特性&#xff0c;迅速成为全球最流行的编程语言之一。Java 的设计哲学之一是提供一套简单且高效的基本数据类型&#xff0c;同时具备强大的字符串处理和数组操作能力。本文将深入探讨 Java 的基本数…

加速决策过程:企业级爬虫平台的实时数据分析

摘要 在当今数据驱动的商业环境中&#xff0c;企业如何才能在海量信息中迅速做出精准决策&#xff1f;本文将探讨企业级爬虫平台如何通过实时数据分析加速决策过程&#xff0c;实现数据到决策的无缝衔接。我们聚焦于技术如何赋能企业&#xff0c;提升数据处理效率&#xff0c;…

NSS [NSSRound#13 Basic]flask?jwt?

NSS [NSSRound#13 Basic]flask?jwt? 开题 注册一下 要admin才能拿flag 看看是如何进行身份验证的 是flask session flask-unsign --decode --cookie .eJwtzjESwyAMBMC_UKfghJCEP-MRICZp7bjK5O9xkX6L_aR9HXE-0_Y-rnik_TXTlsiXEhUXleKGGGuG1jbmogrCEmNirZ7BEB-VJbTfIi-26hQD…

leetCode15三数之和(双指针)

目录 1、题目 2、思路 3、代码 4、总结 1、题目 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为…

MongoDB - 数组更新操作符:$、$[]、$pop、$pull、$push、$each、$sort、$slice、$position

文章目录 1. $1. 更新数组中的值2. 更新数组中的嵌入文档 2. $[]1. 更新数组中的所有元素2. 更新数组中的所有嵌入文档 3. $pop1. 删除数组的第一个元素2. 删除数组的最后一个元素 4. $pull1. 删除所有等于指定值的项2. 删除与指定条件匹配的所有项3. 从文档数组中删除项4. 从嵌…

跨棒距、公法线和齿厚对应关系分析

前面有一期讨论了下滚齿径向进刀量和齿厚的对应关系&#xff1a;《》&#xff0c;有小伙伴又问了&#xff0c;加工时是用跨棒距或者公法线去控制齿厚的&#xff0c;直接给齿厚是无法测量的&#xff0c;如果测一个值再去计算&#xff0c;有点麻烦&#xff0c;有没有他们之间的对…

多多OJ评测系统 在前端脚手架Vue-Cli中设置页面路由

目录 设置页面路由 我们把菜单上的路由改成读取路由文件 设置成export 导出路由 在刚刚的原始路由 index.ts中导入就行了 在这边引入我们的路由文件 我们之后点击菜单 我们的路由文件是这样的 但是没有跳转 写一下事件 接下来要同步路由到菜单项 自己定义监听函数 …

【Springboot starter 组件开发】限流组件 RateLimiter

【Springboot starter 组件开发】限流组件 RateLimiter 一、摘要二、基于guava实现2.1 核心依赖2.2 核心逻辑 三、基于Redis lua脚本实现3.1 核心依赖3.2 核心逻辑 一、摘要 基于guava的RateLimiter&#xff0c;实现限流基于redis lua脚本(推荐&#xff0c;准确性高)&#x…

推荐3款不可错过的实用工具

TouchPro TouchPro是一款运行于Windows系统下的时间属性修改工具&#xff0c;其主要功能是允许用户批量修改文件和文件夹的创建时间、修改时间和访问时间。该软件安装后会集成到资源管理器中&#xff0c;不占用任何系统资源&#xff0c;并支持多级目录与隐藏文件的日期属性批量…

mysql数据迁移,全量和增量

mysql是常用的数据库。数据迁移一般有2种&#xff0c;增量数据和历史数据。 假设我有2个数据库mysqlA和mysqlB。 mysqlB是新的数据库&#xff0c;mysqlA是旧的数据库。 A->B。首先我们选定一个时间为历史数据。将这部分的数据全部输入到B里。 接下来将服务切换的B库。然后…

推荐几款支持AI剪辑并可使用个人视频素材的软件!

最强AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量 其实现在大部分的AI视频剪辑工具都可以实现一键成片&#xff0c;这里给你分享6款可以使用自己的素材实现AI剪辑的工具及其操作方法&#xff01; 一、剪映 剪映…