C++二叉搜索树

C++二叉搜索树

  • 二叉搜索树概念
  • 二叉搜索树操作
    • 结点类的实现
    • 中序遍历实现
    • 二叉搜索树的插入
      • 非递归实现
      • 递归实现
    • 二叉搜索树的查找
      • 非递归实现
      • 递归实现
    • 二叉搜索树的删除
      • 非递归实现
      • 递归实现
    • 构造函数
    • 拷贝构造函数
    • 析构函数
    • 赋值运算符重载
  • 二叉搜索树的应用
  • 二叉搜索树的性能分析

二叉搜索树概念

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

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

在这里插入图片描述

二叉搜索树操作

结点类的实现

为了方便二叉搜索树的实现,我们需要先实现一个节点类,它包含一个左指针,一个右指针,一个结点值。

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

中序遍历实现

为了方便我们随时可以检测我们我们实现的二叉搜索树是否有问题,我们需要实现一个中序遍历来将各结点值打印出来,以便保证二叉搜索树的正确性。

template<class K>
class BSTree
{typedef BSTreeNode Node;
public:void InOrder(){_InOrder(_root);cout << endl;}
private:void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);cout << root->_key << " ";_InOrder(root->_right);}
private:Node* _root;
};

二叉搜索树的插入

插入的具体过程如下:

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

树不为空,插入结点分为以下操作:

  • 待插入结点的值小于根节点的值,向左子树中插入
  • 待插入结点的值大于根节点的值,向右子树中插入
  • 待插入结点的值等于根节点的值,返回false

依次进行插入,直到找到空位置进行插入后者返回false。

非递归实现

我们需要记录根节点的位置,便于最后插入时将待插入节点与它的父节点连接起来,所以我们定义一个parent结点记录位置,在定义一个cur来遍历二叉搜索树:
在这里插入图片描述

代码实现:

bool Insert(const K& key)
{//如果此时插入的为根结点,直接创建新结点if (_root == nullptr){_root = new Node(key);return true;}//定义一个父结点记录位置Node* parent = nullptr;Node* cur = _root;while (cur){//插入值小于结点值,去左子树中找if (cur->_key > key){parent = cur;cur = cur->_left;}//插入值大于结点值,去右子树中找else if(cur->_key < key){parent = cur;cur = cur->_right;}//插入值等于结点值,返回falseelse{return false;}}//创建key所在结点cur = new Node(key);//此时如果key < parent->_key,就在左边插入if (parent->_key > key){parent->_left = cur;}//反之,右边插入else{parent->_right = cur;}return true;
}

递归实现

插入的递归实现其实很简单,依然是小于根节点就去左边插入,大于根节点就去右边插入,但是需要注意的是结点的传递我们需要以引用的方式,因为最后我们是需要将插入结点与前面结点链接起来的:

bool _InSertR(Node*& root, const K& key)
{//为空树,创建一个根结点if (root == nullptr){root = new Node(key);return true;}//key小于根结点的值,左子树中寻找if (root->_key > key){return _InSertR(root->_left, key);}//key大于根结点的值,右子树中寻找else if(root->_key < key){return _InSertR(root->_right, key);}//相等,返回falseelse{return false;}
}
//便于调用子函数进行插入
void InSertR(const K& key)
{_InSertR(_root, key);
}

二叉搜索树的查找

非递归实现

二叉搜索树非递归实现跟插入差不多,就是分别在左右子树中找,找到了就返回true,找不到就返回false:

//查找
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;}//查找值等于结点值,返回trueelse{return true;}}return false;//没有找到,返回false
}

递归实现

bool _FindR(Node*& root, const K& key)
{//为空,就找不到,返回falseif (root == nullptr){return false;}//key小于根结点的值,左子树中寻找if (root->_key > key){return _FindR(root->_left, key);}//key大于根结点的值,右子树中寻找else if (root->_key < key){return _FindR(root->_right, key);}//相等,返回trueelse{return true;}

二叉搜索树的删除

删除的具体过程如下:

  1. 树为空,则不需要删除
  2. 树不空,按二叉搜索树性质查找删除位置

树不为空,存在以下三种情况:

  • 被删除结点左右都为空,此时只需要delete cur,然后让parent结点指向nullptr即可;
    在这里插入图片描述

  • 被删除的结点一边为空,此时就需要就需要判断被删除结点是parent结点的左孩子还是右孩子,
    然后在改变parent结点的指向;
    在这里插入图片描述
    上面两种情况如果我们此时删除的是根结点,我们只需要改变根节点的位置就可以了:
    在这里插入图片描述

  • 被删除的结点两边都不为空,此时就需要我们考虑用替换法来删除,我们可以考虑用被删除结点左子树的最大值或者是右子树的最小值来替换掉被删除结点,因为这样删除以后并不会破坏二叉搜索树的性质,我们以右子树的最小值来替换掉被删除结点为例,我们需要定义一个min结点来记录右子树的最小值,定义一个minParent记录min结点父节点位置。
    在这里插入图片描述
    在这里插入图片描述

非递归实现

//删除
bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){//key小于根结点的值,左子树中寻找if (cur->_key > key){parent = cur;cur = cur->_left;}//key大于根结点的值,右子树中寻找else if (cur->_key < key){parent = cur;cur = cur->_right;}//相等,进行删除else{//被删除结点左孩子为空if (cur->_left == nullptr){//被删除结点为根节点if (cur == _root){//改变根节点指向位置_root = cur->_left;}//被删除结点不为根节点else{//被删除结点为左孩子if (cur == parent->_left){//父结点左指针指向被删除孩子右子树parent->_left = cur->_right;}//被删除结点为右孩子else{//父结点右指针指向被删除孩子右子树parent->_right = cur->_right;}}//删除该结点delete cur;cur = nullptr;}//被删除结点右孩子为空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;}}//删除该结点delete cur;cur = nullptr;}else{//记录待删除结点右子树当中值最小结点的父结点Node* minParent = cur;//记录待删除结点右子树当中值最小的结点Node* min = cur->_right;//寻找待删除结点右子树当中值最小的结点while (min->_left){minParent = min;min = min->_left;}//交换待删除结点与min结点的值swap(cur->_key, min->_key);//此时minParent的左指针指向minif (minParent->_left == min){//让minParent的左指针指向min的右子树minParent->_left = min->_right;}//此时minParent的右指针指向minelse{//让minParent的右指针指向min的右子树minParent->_right = min->_right;}//删除掉min结点delete min;min = nullptr;}return true;}}return false;
}

递归实现

  • 若树为空树,则结点删除失败,返回false。

  • 若所给key值小于树根结点的值,则问题变为删除左子树当中值为key的结点。

  • 若所给key值大于树根结点的值,则问题变为删除右子树当中值为key的结点。

  • 若所给key值等于树根结点的值,则根据根结点左右子树的存在情况不同,进行不同的处理。

bool _EraseR(Node*& root, const K& key)
{//空树,找不到,返回falseif (root == nullptr){return false;}//key小于根结点的值,在左子树中去找if (root->_key > key){return _EraseR(root->_left, key);}//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* min = root->_right;//寻找待删除结点右子树当中值最小的结点while (min->_left){min = min->_left;}//交换待删除结点与min结点的值swap(root->_key, min->_key);//递归进行删除return _EraseR(root->_right, key);}//释放掉待删除结点delete del;return true;}
}

构造函数

构造一个空树即可:

//构造函数
BSTree():_root(nullptr)
{}

拷贝构造函数

我们需要注意点是二叉搜索树的拷贝属于深拷贝,我们需要创建一颗和被拷贝二叉搜索树相同的树:

//拷贝函数
Node* _Copy(Node* root)
{//为空树,直接返回空if (root == nullptr){return nullptr;}//拷贝根结点Node* CopyRoot = new Node(root->_key);//向左递归拷贝左子树CopyRoot->_left = _Copy(root->_left);//向右递归拷贝右子树CopyRoot->_right = _Copy(root->_right);//返回拷贝的树return CopyRoot;
}
//拷贝构造函数
BSTree(const BSTree<K>& t)
{_root = _Copy(t._root);
}

析构函数

二叉搜索树析构函数就是将每个结点都释放掉,但需要注意的是我们这儿要后序遍历进行释放:

void _Destory(Node*& root)
{//为空树,返回上一级if (root == nullptr){return;}//向左递归释放左子树_Destory(root->_left);//向右递归释放右子树_Destory(root->_right);//释放根节点delete root;//root置为nullptrroot = nullptr;
}
//析构函数
~BSTree()
{_Destory(_root);
}

赋值运算符重载

我们使用现代写法来进行赋值运算符重载:

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

二叉搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:

  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。

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

二叉搜索树的性能分析

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

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
在这里插入图片描述
对于有n个结点的二叉搜索树:

最优的情况下,二叉搜索树为完全二叉树,其平均比较次数为:log N;
最差的情况下,二叉搜索树退化为单支树,其平均比较次数为:N / 2;

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

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

相关文章

一键快速还原修复人脸,CodeFormer 助力人脸图像修复

今天在查资料的时候无意间看到了一个很有意思的工具&#xff0c;就是CodeFormer &#xff0c;作者给出来的说明是用于人脸修复任务的&#xff0c;觉得很有意思就拿来实践了一下&#xff0c;这里记录分享一下。 首先对人脸修复任务进行简单的回顾总结&#xff1a; 人脸修复是指…

【Docker】01-Centos安装、简单使用

参考教程&#xff1a; https://www.bilibili.com/video/BV1Qa4y1t7YH/?p5&spm_id_frompageDriver&vd_source4964ba5015a16eb57d0ac13401b0fe77 什么是Docker&#xff1f; Docker是一种开源的容器化平台&#xff0c;用于构建、打包、部署和运行应用程序。它通过使用容…

冠达管理 :主升浪前最后一次洗盘?

随着科技的不断发展&#xff0c;人们关于金融商场的了解也越来越深入。在股市中&#xff0c;洗盘是一个非常重要的概念。洗盘是指许多的股票被清洗出某个价位上的持有者&#xff0c;从而拉低该价位上的股票价格&#xff0c;为后续上涨做出铺垫。而在股市中&#xff0c;主升浪前…

算法工程题(非递减顺序 排列)

* 题意说明&#xff1a; * 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c; * 分别表示 nums1 和 nums2 中的元素数目。 * 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。…

Flutter(九)Flutter动画和自定义组件

目录 1.动画简介2.动画实现和监听3. 自定义路由切换动画4. Hero动画5.交织动画6.动画切换7.Flutter预置的动画过渡组件自定义组件1.简介2.组合组件3.CustomPaint 和 RenderObject 1.动画简介 Animation、Curve、Controller、Tween这四个角色&#xff0c;它们一起配合来完成一个…

2023年高教社杯 国赛数学建模思路 - 复盘:人力资源安排的最优化模型

文章目录 0 赛题思路1 描述2 问题概括3 建模过程3.1 边界说明3.2 符号约定3.3 分析3.4 模型建立3.5 模型求解 4 模型评价与推广5 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 描述 …

maven推包The environment variable JAVA_HOME is not correctly set

解决办法&#xff1a; 打开idea查看jdk安装位置 1.在/etc下面创建&#xff08;如果存在就是更新&#xff09;launchd.conf。里面添加一行&#xff1a; setenv JAVA_HOME /Library/Java/JavaVirtualMachines/jdk1.8.0_351.jdk/Contents/Home #JAVA_HOME后面是我的java安装路径…

Python正则表达式简单教程

当涉及到处理文本数据时&#xff0c;正则表达式是一个非常有用的工具。它可以用于在字符串中进行模式匹配、搜索、替换等操作。以下是一个简单的Python正则表达式教程&#xff0c;从基础开始介绍如何使用正则表达式。 什么是正则表达式&#xff1f; 正则表达式&#xff08;Re…

【跟小嘉学 Rust 编程】二十、进阶扩展

系列文章目录 【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学…

windows安装新openssl后依然显示旧版本

1、Windows环境下安装升级新版本openssl后&#xff0c;通过指令openssl version -a查看版本号&#xff1a;如下 这个版本号还是是以前的老版本&#xff0c;看来得把原先的老版本删除掉才可以生效&#xff0c;但是不知道在哪里。 2、网上找了老半天也没找到答案&#xff0c;最后…

JS三座大山 —— 原型和原型链

系列文章目录 内容链接2023前端面试笔记HTML52023前端面试笔记CSS3 文章目录 系列文章目录前言一、原型是什么&#xff1f;二、原型链是什么&#xff1f;2.1 原型链全方面解析2.2 为什么构造函数也有原型&#xff1f; 总结 前言 理解原型和原型链可以帮助我们更好地理解 Java…

后台管理系统:项目路由搭建与品牌管理

路由的搭建 先删除一些不需要的界面 然后发现跑不起来&#xff0c;我们需要去配置 删减成这样&#xff0c;然后自己新建需要的路由组件 改成这样&#xff0c;这里要注意。我们是在layout这个大的组件下面的&#xff0c;meta 中的title就是我们侧边栏的标题&#xff0c;icon可…

积分游戏小程序模板源码

积分游戏小程序模板源码是一款可以帮助用户快速开发小程序的工具&#xff0c;此模板源码包含五个静态页面&#xff0c;分别是首页、任务列表、大转盘、猜拳等五个页面&#xff0c;非常适合进行积分游戏等相关开发。 此模板源码的前端部分非常简单易用&#xff0c;用户可以根据…

mongodb 分片集群部署

文章目录 mongodb 分片部署二进制安装三台config 配置shard 分片安装shard1 安装shard2 安装shard3 安装mongos 安装数据库、集合启用分片创建集群认证文件创建集群用户部署常见问题 mongodb 分片部署 二进制安装 mkdir -p /data/mongodb tar xvf mongodb-linux-x86_64-rhel7…

数据通信——传输层TCP(可靠传输原理的ARQ)

引言 上一篇讲述了停止等待协议的工作流程&#xff0c;在最后提到了ARQ自动请求重传机制。接下来&#xff0c;我们就接着上一篇的篇幅&#xff0c;讲一下ARQ这个机制 还是这个图来镇楼 ARQ是什么&#xff1f; 发送端对出错的数据帧进行重传是自动进行的&#xff0c;因而这种…

C语言每日一练----Day(13)

本专栏为c语言练习专栏&#xff0c;适合刚刚学完c语言的初学者。本专栏每天会不定时更新&#xff0c;通过每天练习&#xff0c;进一步对c语言的重难点知识进行更深入的学习。 今日练习题关键字&#xff1a;数字颠倒 单词倒排 &#x1f493;博主csdn个人主页&#xff1a;小小uni…

k8s 查看加入主节点命令 k8s重新查看加入节点命令 k8s输入删除,重新查看加入命令 kuberadm查看加入节点命令

1. 使用kuberadm 安装成功后&#xff0c;clear清除了屏幕数据&#xff0c;加入命令无法查看&#xff0c;使用如下&#xff0c;重新查看node如何加入主节点命令&#xff1a; kubeadm token create --print-join-command --ttl 0 2.画圈的全部是&#xff0c;都复制&#xff0c;在…

css中文本阴影特效

文字颜色渐变 .text-clip{color:transparent;font-size: 40px;font-weight: bold;background: linear-gradient(45deg, rgba(0,173,181,1) 0%, rgba(0,173,181,.4) 100%);-webkit-background-clip: text; } 文字模糊 .text-blurry{text-align: center;color: transparent;text-…

Android修改默认gradle路径

Android Studio每次新建项目&#xff0c;都会默认在C盘生成并下载gradle相关文件&#xff0c;由于C盘空间有限&#xff0c;没多久C盘就飘红了&#xff0c;于是就需要把gradle相关文件转移到其他盘 1、到C盘找到gradle文件 具体路径一般是&#xff1a;C:\Users\用户\ .gradle …

[第七届蓝帽杯全国大学生网络安全技能大赛 蓝帽杯 2023]——Web方向部分题 详细Writeup

Web LovePHP 你真的熟悉PHP吗&#xff1f; 源码如下 <?php class Saferman{public $check True;public function __destruct(){if($this->check True){file($_GET[secret]);}}public function __wakeup(){$this->checkFalse;} } if(isset($_GET[my_secret.flag]…