【C++】---二叉搜索树

【C++】---二叉搜索树

  • 一、二叉搜索树概念
  • 二、二叉搜索树操作(非递归)
    • 1.二叉搜索树的查找 (非递归)
      • (1)查找
      • (2)中序遍历
    • 2.二叉搜索树的插入(非递归)
    • 3.二叉搜索树的删除(非递归)
  • 三、二叉搜索树操作(递归)
    • 1.二叉搜索树的查找(递归)
    • 2.二叉搜索树的插入(递归)
    • 3.二叉搜索树的删除(递归)
  • 四、二叉搜索树的默认成员函数
    • 1.构造
    • 2.拷贝构造
    • 3.赋值运算符重载
    • 4.析构
  • 五、K模型和KV模型搜索树
    • 1.K模型搜索树
    • 2.KV模型搜索树
  • 六、二叉搜索树性能分析

在这里插入图片描述

一、二叉搜索树概念

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

  1. 如果它的左子树不为空,那么左子树上所有节点的值都小于根结点的值。
  2. 如果它的右子树不为空,那么右子树上所有节点的值都大于根节点的值。
  3. 它的左右子树也是二叉搜索树。

在这里插入图片描述

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

比如说:这个数组都可以将它化为二叉搜索树

在这里插入图片描述

总结:在左子树值比根小,右子树值比根大。 当树走中序遍历时,序列都是有序的

二叉搜索树 的 结构定义:


#include<iostream>
using namespace std;template<class K>
struct BSTreeNode
{BSTreeNode<K>* _left;BSTreeNode<K>* _right;K _key;BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
private:Node* _root;
public:BSTree():_root(nullptr){}
};

二、二叉搜索树操作(非递归)

1.二叉搜索树的查找 (非递归)

利用二分查找的方法,借助我们去二叉搜索树中查找节点。

在这里插入图片描述
在这里插入图片描述
查找的时间复杂度:最坏的情况,就是查找高度(h=logN)次,就可以判断一个值在不在节点里面。

(1)查找

查找的思路:

  1. key比当前结点的值小,往左走!
  2. key比当前结点的值大,往右走!
  3. key==当前结点的值,就找到了!

在这里插入图片描述

// 查找:Node* Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key){cur = cur->_left;}else if (key > cur->_key){cur = cur->_right;}else{return cur;// 找到了!}}return nullptr;// 遍历完了,都还没找到!}

(2)中序遍历

由于根节点_root是私有成员变量,如果在main函数里面来进行中序遍历的话,这就是在类外对私有成员进行访问,这是不合法的!

所以说我们要解决这个问题,可以用这样:
在类的public内的 中序遍历 InOrder 里面 再套一层私有的中序遍历:_InOrder,这样,_InOrder身为私有函数,就可以访问:私有变量_root!

private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << endl;_InOrder(root->_right);}
public:// 中序遍历:void InOrder() //这个函数 类外可以直接访问!{_InOrder(_root); // 这个函数,是 私有函数 对 私有成员 的访问!cout << endl;}

2.二叉搜索树的插入(非递归)

插入节点分两步:

(1)找位置

    ①key比当前节点值大,向左走②key比当前节点值小,向右走③key等于当前节点值,该节点值已经存在,插入失败

(2)插入

    ①key比父亲节点值小就插入父亲左子树②key比父亲节点值大就插入父亲右子树

由于插入后,要将节点链接到树中,因此要定义parent节点,用来链接新节点:

在这里插入图片描述

// 插入:bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;// (1) 找到插入的位置while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{return false;// 二叉搜索树不允许数据冗余!}}cur = new Node(key);// (2) 判断if (key<parent->_key){parent->_left = cur;}else{parent->_right = cur;}return true;}

3.二叉搜索树的删除(非递归)

非递归删除:

(1)找位置

    ①key比当前节点值大,向左走②key比当前节点值小,向右走③key等于当前节点值,找到了,准备删除

(2)删除,有两种删除方法:非递归和递归

    非递归删除: ①该节点没有孩子,即该节点是叶子节点,删除节点后把父亲指向自己的指针置空

在这里插入图片描述

    ②该节点有一个孩子,就把该节点的孩子节点的链接给该节点的父亲,顶替自己的位置,①可以当成②的特殊情况

在这里插入图片描述

    ③该节点有两个孩子,找比它自己的左孩子大,比它自己的右孩子小的节点替换它(也就是拿它的左子树的最大节点或右子树的最小节点替换它),替换之后,该节点就只有一个孩子或没有孩子了,就变成①或②了。

在这里插入图片描述

// 删除bool erase(const K& key){Node* cur = _root;Node* parent = nullptr;// (1) 找到插入的位置while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{break;}}// 1、2、 (子 代替 父亲的位置)// 大前提:如果要删除的节点,left为空if (cur->_left == nullptr){// 如果要删除根!if (cur == _root){_root = cur->_right;// 那就让cur的右当根}// 如果要删除的不是根!else{// 如果要删除的节点cur,在父亲的左边。// 因为是替代法,所以说要让 子 的位置代替 父亲 的位置,但是 子 的位置只有_right存在,所以说会把_right的位置放到即将要删除cur的位置。if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}// 大前提:如果要删除的节点,right为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{// 因为是替代法,所以说要让 子 的位置代替 父亲 的位置,但是 子 的位置只有_left存在,所以说会把_left的位置放到即将要删除cur的位置。if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}// 3、要删除的cur不只有一个节点。可能有多个节点,甚至整个指子树// 找到要删除节点cur,左子树最大的节点,右子树最小的节点,来代替cur的位置。else{// 要么找cur左子树中的max,要么就找右子树中的min// 这里 以 RightMin为例!// (1)找到 RightMin (就像找 cur那样)Node* RightMin = cur->_right;Node* RightMinParent = cur; // 定义 RightMinParent 为了方便后续节点的连接。while (RightMin->_left){RightMinParent = RightMin;RightMin = RightMin->_left;}// (2)找到了 就交换!swap(RightMin->_key, cur->_key);// (3) 交换完后 就链接!if (RightMinParent->_left == RightMin)RightMinParent->_left = cur;elseRightMinParent->_right = cur;// 链接完成!delete cur;}return true;}

递归删除:

相对于非递归,只需要修改找到了要修改的代码:找到了后不需要管cur到底左为空、右为空、还是左右都不为空

① 找要删除节点的右子树的最小节点并把它的值保存起来

② 删除右子树的最小节点

③ 把要删除的节点值替换成右子树的最小节点值

在这里插入图片描述

                else//左右都不为空,替换法删除{//找右子树最小节点Node* minRight = cur->_right;while (minRight->_left){minRight = minRight->_left;}//用min保存右子树最小节点的值K min = minRight->_key;//递归调用自己去替换删除节点,一定会走到左为空的情况处理this->Erase(min);//删除完毕替换节点之后,把cur的值替换成mincur->_key = min;}

三、二叉搜索树操作(递归)

理解了非递归操作以后, 递归操作就很简单了:

#include<iostream>
using namespace std;//树的节点可以支持多种类型
template<class K>
//树节点结构
struct BSTreeNode
{BSTreeNode<K>* _left;//左指针BSTreeNode<K>* _right;//右指针K _key;//值//构造函数BSTreeNode(const K& key):_left(nullptr), _right(nullptr), _key(key){}
};template<class K>
class BStree//树结构
{typedef BSTreeNode<K> Node;
public://递归查找Node* 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:Node* _root;
};

由于_root是私有的,可以把递归子函数查找、插入、删除都定义成私有的

1.二叉搜索树的查找(递归)

private://查找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;}}

2.二叉搜索树的插入(递归)

	//插入 加了&,root是_root的别名,修改root就直接修改到上一层调用,不用找父亲bool _InsertR(Node*& root, const K& key){if (root == nullptr)//找到位置了{root = new Node(key);return true;}if (key < root->_key)//到左子树去找位置{_InsertR(root->_left, key);}else if (key > root->_key)//到右子树去找位置{_InsertR(root->_right, key);}else//已存在,无需插入{return false;}}

3.二叉搜索树的删除(递归)

递归删除:和二叉树的删除(非递归)一样,找到后的删除也有两种方式,递归和非递归

找到后的非递归删除:

    //插入 加了&,root是_root的别名,修改root就直接修改到上一层调用,不用找父亲	bool _EraseR(Node*& root, const K& key){if (root == nullptr)//没找到{return false;}if (key < root->_key)//到左子树去找{_EraseR(root->_left, key);}else if (key > root->_key)//到右子树去找{_EraseR(root->_right, key);}else{//找到了,root就是要删除的节点if (root->_left == nullptr)//root左为空{Node* del = root;root = root->_right;delete del;}else if (root->_right == nullptr)//root右为空{Node* del = root;root = root->_left;delete del;}else//root左右都不为空{//找到右子树最左节点替换Node* minParent = root;Node* minRight = root->_right;while (minRight->_left){minParent = minRight;minRight = minRight->_left;}//保存替换节点的值cur->_key = minRight->_key;//链接if (minParent->_left == minRight){minParent->_left = minRight->_right;}else{minParent->_right = minRight->_right;}//删除delete minRight;}return true;}}

找到后的递归删除:

			else//root左右都不为空{				//找右子树最左节点Node* minRight = root->_right;while (minRight->_left){minRight = minRight->_left;}//保存右子树最左节点的值K min = minRight->_key;//使用递归方法删除右子树最左节点_Erase(root->_right, min);}

四、二叉搜索树的默认成员函数

现在还剩下二叉搜索树的构造、拷贝构造、赋值运算符重载、析构函数。

1.构造

public://构造函数需要将根初始化为空就行了BSTree():_root(nullptr){}

2.拷贝构造

拷贝构造利用递归调用子函数不断拷贝节点:

	//拷贝构造BSTree(const BSTree<K>& t){_root = t.copy(t._root);}

在子函数处:

	Node* _copy(Node* root){if (root == nullptr)//如果根为空,直接返回{return;}Node* copyNode = new Node(root->_key);//创建根节点copyNode->_left = _copy(root->_left);//递归拷贝左子树节点copyNode->_right = _copy(root->_right);//递归拷贝右子树节点return copyNode;//返回根}

3.赋值运算符重载

借助拷贝构造用现代写法写:

	//赋值运算符重载(现代写法)BSTree& operator=(const BSTree<K>& t){swap(_root,t._root);return *this;}

4.析构

递归调用子函数去析构

	//析构~BSTree(){_Destroy(_root);_root = nullptr;}

在子函数处:

	_Destroy(root){if (root == nullptr){return;}_Destroy(root->_left);_Destroy(root->_right);delete root;}

五、K模型和KV模型搜索树

1.K模型搜索树

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

2.KV模型搜索树

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

1、比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
2、再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对。

改造二叉搜索树为KV结构的代码

#pragma once
#include<iostream>
#include<string>
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;// pair<K, V> _kv;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:// logNbool Insert(const K& key, const V& value){if (_root == nullptr){_root = new Node(key, value);return true;}Node* parent = nullptr;Node* cur = _root;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, value);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}Node* Find(const K& key){Node* cur = _root;while (cur){if (cur->_key < key){cur = cur->_right;}else if (cur->_key > key){cur = cur->_left;}else{return cur;}}return cur;}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{// 删除// 左为空,父亲指向我的右if (cur->_left == nullptr){//if(parent == nullptr)if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr){//if(parent == nullptr)if (cur == _root){_root = cur->_left;}else{// 右为空,父亲指向我的左if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;}else{// 左右都不为空,替换法删除// // 查找右子树的最左节点替代删除Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);if (rightMinParent->_left == rightMin)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}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 << ":" << root->_value << endl;_InOrder(root->_right);}private:Node* _root = nullptr;};

六、二叉搜索树性能分析

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

在这里插入图片描述


好了,今天的分享就到这里了
如果对你有帮助,记得点赞👍+关注哦!
我的主页还有其他文章,欢迎学习指点。关注我,让我们一起学习,一起成长吧!
在这里插入图片描述

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

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

相关文章

Java 实现二叉搜索树 代码

新建文件 创建TreeNode类&#xff0c;实例化 直接在BinarySearchTree类里面写就可以 static class TreeNode {public int key;public TreeNode left;public TreeNode right;TreeNode(int key) {this.key key;}}public TreeNode root; 插入节点 insert public boolean inser…

Spring创建对象的多种方式

一、对象分类 简单对象&#xff1a;使用new Obj()方式创建的对象 复杂对象&#xff1a;无法使用new Obj()方式创建的对象。例如&#xff1a; 1. AOP创建代理对象。ProxyFactoryBean; 2. Mybatis中的SqlSessionFactoryBean; 3. Hibernate中的SessionFactoryBean。二、创建对象方…

创新案例 | 持续增长,好孩子集团的全球化品牌矩阵战略与客户中心设计哲学

探索好孩子集团如何通过创新设计的全球化品牌矩阵和以客户为中心的产品策略&#xff0c;在竞争激烈的母婴市场中实现持续增长。深入了解其品牌价值观、市场定位策略以及如何满足新一代父母的需求。本文旨在为中高级职场人士、创业家及创新精英提供深度见解&#xff0c;帮助他们…

最新上市公司控制变量大全(1413+指标)1990-2023年

数据介绍&#xff1a;根据2023年上市公司年报数据进行更新&#xff0c;包括基本信息、财务指标、环境、社会与治理、数字化转型、企业发展、全要素生产率等1413指标。数据范围&#xff1a;A股上市公司数据年份&#xff1a;1990-2023年指标数目&#xff1a;1413个指标&#xff0…

在云中确保安全的五个技巧

随着采用云计算战略并开始充分意识到云计算技术可以提供的回报&#xff0c;企业可以做些什么来改善他们的风险状况?以下是德迅云安全在云中确保安全的五个技巧。 德迅云安全对如何在云计算基础设施中确保安全的五个技巧进行了阐述和分析。 在当今的混合工作环境中&#xff0c…

UG NX二次开发(C#)-UFun函数-利用UFPart.Export导出模型中的对象并创建一个新的part

文章目录 1、前言2、UF_PART_export函数定义3、UF_PART_export_with_options函数定义4、代码1、前言 在UG NX 10.0二次开发中,需要用到将装配体中通过几何建模创建的对象独立创建一个part文件,所以查找了下UFun函数,即是UF_PART_export 和UF_PART_export_with_options两个函…

在Windows 10中,如何利用命令提示符删除应用程序

如果你使用的是Windows 10&#xff0c;并且需要释放一些磁盘空间&#xff0c;你可以直接从命令提示符卸载不再使用的应用程序。以下是操作方法。 首先&#xff0c;你必须以管理员身份运行命令提示符才能卸载程序。在“Windows搜索”框中&#xff0c;键入“cmd”或“命令提示符…

NVR对接三方相机预览黑屏问题案例

一、 问题现象 【问题现象】NVR接入三方相机,通道状态显示在线,但本地、web预览显示黑屏。更换H.264&#xff0c;H.265均预览黑屏&#xff0c;且NVR侧的萤石云手机APP预览报错260025。 【现场拓扑】现场拓扑如下 &#xff08;1&#xff09; IPC使用onvif协议添加至NVR&#xff…

程序猿转型做项目经理一定要注意这 5 个坑

前言 国内的信息系统项目经理&#xff0c;很多都是从技术骨干转型的&#xff0c;我就是这样一路走过来的&#xff0c;这样有很多好处&#xff0c;比如技术过硬容易服众、熟悉开发流程更容易把控项目进度和质量、开发过程中碰到难题时更好组织攻坚等等&#xff0c;但是所谓成也…

SpringBootWeb 篇-深入了解会话技术与会话跟踪三种技术(Cookie 会话跟踪、Session 会话跟踪与 JWT 令牌会话跟踪)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 会话技术 2.0 会话跟踪 2.1 会话跟踪 - Cookie 2.1.1 客户端获取 Cookie 的流程 2.1.2 Cookie 会话跟踪的特点 2.2 会话跟踪 - Session 2.2.1 客户端获取 SESSION…

C++设计模式-单例模式,反汇编

文章目录 25. 单例模式25.1. 饿汉式单例模式25.2. 懒汉式单例模式25.2.1. 解决方案125.2.2. 解决方案2 &#xff08;推荐写法&#xff09; 运行在VS2022&#xff0c;x86&#xff0c;Debug下。 25. 单例模式 单例即该类只能有一个实例。 应用&#xff1a;如在游戏开发中&#x…

【漏洞复现】大华智能物联综合管理平台 log4j远程代码执行漏洞

0x01 产品简介 大华ICC智能物联综合管理平台对技术组件进行模块化和松耦合&#xff0c;将解决方案分层分级&#xff0c;提高面向智慧物联的数据接入与生态合作能力。 0x02 漏洞概述 大华ICC智能物联综合管理平台/evo-apigw/evo-brm/1.2.0/user/is-exist 接口处存在 l0g4i远程…

【1.文件和目录相关(上)】

一、Linux的文件系统结构 1、Linux文件系统就是一个树形的分层组织结构。 2、文件系统层次结构标准FHS&#xff1a;用于规范文件目录命名和存放标准。 &#xff08;1&#xff09;/bin:是二进制英文缩写。 &#xff08;2&#xff09;/boot:存放的是系统启动时要用到的程序。 …

如何配置才能连接远程服务器上的 redis server ?

文章目录 Intro修改点 Intro 以阿里云服为例。 首先&#xff0c;我在我买的阿里云服务器中以下载源码、手动编译的方式安装了 redis-server&#xff0c;操作流程见&#xff1a;Ubuntu redis 下载解压配置使用及密码管理 && 包管理工具联网安装。 接着&#xff0c;我…

Java中的ORM框架——myBatis

一、什么是ORM ORM 的全称是 Object Relational Mapping。Object代表应用程序中的对象&#xff0c;Relational表示的是关系型数据库&#xff0c;Mapping即是映射。结合起来就是在程序中的对象和关系型数据库之间建立映射关系&#xff0c;这样就可以用面向对象的方式&#xff0c…

【UE 反射】反射的原理是什么?如何使用机制?

目录 0 拓展0.1 静态类型检查0.1.1 静态类型检查的主要原理0.1.2 编译器的工作流程0.1.3 静态类型检查的优点和缺点0.1.4 示例0.1.5 C也可以在运行时类型检查RTTI基本原理RTTI的实现RTTI的工作流程RTTI的限制 0.2 运行时动态类型检查0.2.1 主要特点0.2.2 动态类型检查的实现0.2…

网页中的音视频裁剪拼接合并

一、需求描述 项目中有一个配音需求&#xff1a; 1&#xff09;首先&#xff0c;前台会拿到一个英语视频&#xff0c;视频的内容是A和B用英语交流&#xff1b; 2&#xff09;然后&#xff0c;用户可以选择为某一个角色配音&#xff0c;假如选择为A配音&#xff0c;那么视频在播…

命令行解析器浅解

1、什么叫解析器&#xff1f; 解析器&#xff08;parser&#xff09;是一种程序或组件&#xff0c;用于分析输入的数据&#xff0c;并将其转换为更易于处理的格式。解析器在计算机科学中有广泛的应用&#xff0c;特别是在编译器、解释器、自然语言处理和数据格式转换等领域。 1…

内存函数<C语言>

前言 前面两篇文章介绍了字符串函数&#xff0c;不过它们都只能用来处理字符串&#xff0c;C语言中也内置了一些内存函数来对不同类型的数据进行处理&#xff0c;本文将介绍&#xff1a;memcpy()使用以及模拟实现&#xff0c;memmove()使用以及模拟实现&#xff0c;memset()使用…

vue3学习(四)

前言 接上篇学习笔记&#xff0c;分享3个内置组件&#xff1a;动态组件、缓存组件、分发组件基本用法。大家一起通过code的示例&#xff0c;从现象理解,注意再次理解生命周期。 一、code示例 组件A&#xff1a;CompA <script setup> import {onMounted, onUnmounted} f…