【C++与数据结构】搜索二叉树(BinarySearchTree)

一、概念:

二叉搜索树又称为二叉排序树,因为它具有以下性质:

1、如果二叉树的左子树不为空,那么它左子树的任意一个节点的值都小于根节点。

2、如果二叉树的右子树不为空,那么它右子树的任意一个节点的值都大于根节点。

3、并且这个根节点的左子树和右子树都是二叉搜索树。

4、二叉搜索树可以为空树。

二、二叉树的操作:

1、构建二叉树节点:

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

如上就是构建出每一个二叉树的节点,接下来定义一个BSTree类,在里面进行操作的基础实现即可。

在类里面的操作中,有两类函数,一类是在类里面做私有的,另一类是在类里面做公有的,也就是给外界访问的,因为在递归实现中外界不方便访问私有成员_root,这样在类中私有函数作为公有函数的子函数,公有函数只需调用对应的子函数即可。

2、析构与构造:

在BSTree类中只定义一个成员变量_root根节点,在构造函数中进行初始化列表,将_root指向空。

拷贝构造:

思路:

拷贝构造中,直接给_root调用私有函数Copy ,

Copy实现思路:

这里采用递归方式,采用后序遍历

之后在拷贝构造中调用即可。

public:BSTree(const BSTree<K>& t){_root = Copy(t._root);}
private:Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* copynode = new Node(root->_key);copynode->_left = Copy(root->_left);copynode->_right = Copy(root->_right);return copynode;}

析构函数:

和拷贝构造一样,析构函数通过调用子函数的实现,这里依然用后序遍历来进行删除操作.

public:~BSTree(){Destory(_root);}
private:void Destory(Node*& root){if (root == nullptr)return;return Destory(root->_left);return Destory(root->_right);delete root;root = nullptr;}

赋值操作:

采用现代写法直接进行交换

    BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}

3、中序遍历打印:

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);}

4、插入操作:

循环实现:

思路:

首先考虑有可能为空树的情况,所以先判断是否为空树,如果是就直接new

如果不是空树,那么就定义两个指针一个cur遍历这个树,另一个为parent,指向cur的父节点

接着循环遍历如果cur->key小于要插入的key那么就往右找,如果cur->key大于要插入的key那么就往左找,如果相等就返回false,不能插入相等节点。

找到之后对cur进行new,接着判断cur是parent的左孩子还是右孩子,最后链接即可。

	bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* parent = nullptr;Node* cur = _root;while (cur){//这个if是找到nullptr的位置if (cur->_key < key){parent = cur;cur = cur->_right;}else if (cur->_key > key){parent = cur;cur = cur->_left;}else{return false;}}//找到之后将这个key链接到树中//cur->_key = key;cur = new Node(key);if (parent->_key < key){parent->_right = cur;}else{parent->_left = cur;}return true;}

递归实现:

思路:

在传参数的时候传Node* root的引用,这样在下面递归的时候root就是上一层root的指向左孩子的指针或者是指向右孩子的指针,这样就可以直接改变指向的节点值。

最开始是判断root节点是否为空,这部分和循环实现是一样的,

接着判断key若比root的key大就往右边递归,若比root的key小就往左边递归,下面递归后,root是上一层root的指向左孩子的指针或者是指向右孩子的指针,这样判断发现root的左孩子或者右孩子为空,这样对root的左边的指针或者右边的指针的别名进行赋值就链接上了。

public: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->_right, key);}else if (root->_key > key){return _InsertR(root->_left, key);}else{return false;}}

5、查找:

循环实现:

定义一个cur从根节点开始遍历,和上面差不多。

	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;}

递归实现:

和上面大差不差,在最前面加上判空返回,接着如果待查找key大于此时cur的key,那么就向右递归走,如果小于就向左递归走。

	
public: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->_right, key);}else if (root->_key > key){return _FindR(root->_left, key);}else{return true;}}

6、删除操作:

循环实现:

思路:

删除操作需要cur从root开始寻找待删除的,找到后会有三种情况,

1、待删除的节点左边是空,

2、右边是空,

3、或者两边都不是空,

如果两边都是空可以看做左边是空或者右边是空,就不需要考虑这种情况了,

如果是左边为空,那么在这个if里面,在进行判断,毕竟可能是根节点,此时parent就是nullptr,如果不是就判断cur在parent的左边还是右边,在左边就把parent的左指针指向cur的右孩子,在右边就把parent的右指针指向cur的右孩子。

 

如果是右边为空和左边思路是一样的。

 

如果是两边都不为空,就比较麻烦了,不能够直接删除,这样的话二叉树的结构就会被破坏了,所以需要将待删除节点的左子树的最大节点或者右子树的最小节点和待删除节点交换,之后再删除待删除的节点,这样就不会破坏二叉树的结构了。

 

下面在实现的时候采用待删除的左边的最大节点和待删除节点交换,交换后依然要判断此时左边最大是在父节点的左边还是右边,最后记得将左边最大的赋值个cur,毕竟最后是delete 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//cur->key == key就是找到了删除的位置{//左边为空if (cur->_left == nullptr){if (cur == _root){_root = cur->_+right;}else{//在判断此时的cur在parent的左边还是右边if (parent->_right == cur){parent->_right = cur->_right;}else//cur在parent的左边{parent->_left = cur->_right;}}}//右边为空else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{//在判断此时的cur在parent的左边还是右边if (parent->_right == cur){parent->_right = cur->_left;}else//cur在parent的左边{parent->_left = cur->_left;}}}else//左右都不为空{parent = cur;Node* leftMax = cur->_left;//找到最左边的最大节点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;}

递归实现:

在形参中也传Node* root的引用,这样后面直接修改指针就可以了。

首先,在递归最开始如果碰到空就返回false,没找到,接着总体思路是差不多的,

先找key的值,如果找的key比此时root大就往右边递归,小就往左边递归,找到了之后在删除之前保存root的值,这样的话才在后面可以直接删除,不然的话在后面就找不到了,

依然是上面的那几种情况,这里就不用父节点了,有引用直接修改指向就可以了。

public:bool EraseR(const K& key){return _EraseR(_root, key); }
private:bool _EraseR(Node*& root, const K& key){//在递归中如果没找到就返回falseif (root == nullptr)return false;//找key的值if (root->_key < key){return _EraseR(root->_right, key);}else if (root->_key > key){return _EraseR(root->_left, key);}else//找到了{//找到了之后就进行交换删除//在删除之前保存root的值,这样的话才在后面可以直接删除,不然的话在后面就找不到了Node* del = root;//依然是3种情况//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(leftMax->_key, root->_key);return _EraseR(root->_left, key);}delete del;return true;}}

三、二叉树的应用:

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方
式在现实生活中非常常见:
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
现次数就是<word, count>就构成一种键值对。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

C++类和对象第一关

一&#xff1a;类的定义 &#xff08;一&#xff09;类的定义 &#xff08;1&#xff09;类的定义格式&#xff1a; class name{ // 类成员变量 // 类方法&#xff08;函数&#xff09; }; class是定义类的关键字&#xff0c;name为定义的类的名字&#xff0c;后面的花括号…

助力降本增效,ByteHouse打造新一代云原生数据仓库

随着数据量的爆炸式增长、企业上云速度加快以及数据实时性需求加强&#xff0c;云原生数仓市场迎来了快速发展机遇。 据 IDC、Gartner 研究机构数据显示&#xff0c;到 2025 年&#xff0c;企业 50% 数据预计为云存储&#xff0c;75% 数据库都将运行在云上&#xff0c;全球数据…

DK5V100R10SL贴片TO252功率12V4.3A同步整流芯片

概述DK5V100R10SL是一款简单高效率的同步整流芯片&#xff0c;只有A&#xff0c;K两个功能引脚&#xff0c;分别对应肖特基二极管PN管脚。芯片内部集成了100V功率NMOS管&#xff0c;可以大幅降低二极管导通损耗&#xff0c;提高整机效率&#xff0c;取代或替换目前市场上等规的…

双十一数码产品有哪些? 2024年度双十一数码好物推荐

每年双十一来临都是更新手机、平板或者电脑、耳机的绝佳时机。年末也让一年来发布的新机器有了更大的优惠空间再加上平台补贴&#xff0c;绝对是实打实的划算。今天给大家总结了几款双十一价格刷新新低的数码好物&#xff0c;真的要看过再下单&#xff0c;不然买贵就吃亏了。 …

UGUI动态元素大小的滑动无限列表

效果与使用说明 效果 可以滑动无限列表&#xff08;严格来说也和常规的不太一样&#xff09;可以通过曲线调整元素大小 使用说明 列表元素位于脚本挂载处的直接子级最大的元素位于脚本挂载元素的pivot处水平列表的对齐依据是所有元素pivot都在一条线上默认在最左侧和最右侧元…

kafka下载配置

下载安装 参开kafka社区 zookeeperkafka消息队列群集部署https://apache.csdn.net/66c958fb10164416336632c3.html 下载 kafka_2.12-3.2.0安装包快速下载地址分享 官网下载链接地址&#xff1a; 官网下载地址&#xff1a;https://kafka.apache.org/downloads 官网呢下载慢…

基于Node.js+Express+MySQL+VUE实现的计算机毕业设计共享单车管理网站

单车信息选择骑行 骑行状态留言公告/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序 功能如下&#xff1a; 一、开发目标 在共享经济日益盛行的今天&#xff0c;共享单车作为一种绿色、便捷的出行方式&#xff0c;已经深入人们的日常生活。然而&#xff0c;随着共享…

解读滁州少儿自闭症寄宿制学校:为孩子重新定义未来

为自闭症儿童点亮希望之光&#xff1a;星贝育园自闭症儿童寄宿制学校的温暖之旅 在繁华与喧嚣交织的都市一隅&#xff0c;广州的星贝育园自闭症儿童寄宿制学校如同一座温馨的灯塔&#xff0c;为那些在社交与沟通海洋中迷失方向的小小航船指引着方向&#xff0c;重新定义了他们…

win 录屏软件有哪些?5个软件帮助你快速进行电脑录屏。

win 录屏软件有哪些&#xff1f;5个软件帮助你快速进行电脑录屏。 在 Windows 系统上录屏操作十分常见&#xff0c;无论是制作教程、记录游戏片段&#xff0c;还是录制会议和演示文稿&#xff0c;都需要一个高效、稳定的录屏软件。以下是五款适合 Windows 系统的录屏软件&…

docker - maven 插件自动构建镜像(构建镜像:ebuy-docker:v2.0)

文章目录 1、docker服务端开启远程访问2、在pom.xml文件plugins下添加Maven的docker插件3、编写dockerfile文件4、执行maven的打包命令5、查看 镜像 ebuy-docker:v2.06、创建 容器 ebuy-dockerv2.0 上面手动构建镜像的过程比较繁琐&#xff0c;使用Maven的docker插件可以实现镜…

混合专家模型在大模型微调领域进展

前言&#xff1a;随着大规模语言模型&#xff08;LLM&#xff09;的快速发展&#xff0c;人工智能在自然语言处理领域取得了巨大的进步。在将大模型转化为实际生产力时&#xff0c;不免需要针对实际的任务对大模型进行微调。然而&#xff0c;随着模型规模的增长&#xff0c;微调…

【最新华为OD机试E卷-支持在线评测】分苹果(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

[Linux][进程][进程的七种状态]

进程状态是操作系统用来管理进程的一种手段&#xff0c;操作系统通过动态的调整进程状态来合理的分配资源&#xff0c;维护整个系统的生态。 // Linux内核对进程各个状态的定义&#xff0c;Linux系统的进程的状态不考虑/* * The task state array is a strange "bitmap&qu…

尚庭公寓-接口定义

5. 接口定义 5.1 后台管理系统接口定义 5.1.1 公寓信息管理 5.1.1.1 属性管理 属性管理页面包含公寓和房间各种可选的属性信息&#xff0c;其中包括房间的可选支付方式、房间的可选租期、房间的配套、公寓的配套等等。其所需接口如下 房间支付方式管理 页面如下 所需接口如…

【笔记】如何将本地的.md变成不影响阅读的类pdf模式

在1处搜索markdown viewer 在2处勾选url复选框 将需要阅读的md文件的本地路径去除双引号&#xff08;如果没有双引号不必做任何处理&#xff09; 直接放进浏览器url地址栏 正常显示图片与文字 解决

如何将泰语入门提高到精通呢?

要精通泰语&#xff0c;需要从基础的字母和发音开始学习&#xff0c;并通过积累词汇、频繁练习口语、沉浸在语言环境中来不断提高。参加在线课程或找专业教师进行系统性学习也很有帮助。此外&#xff0c;利用各种教材和在线资源&#xff0c;以及保持持续和一致的学习态度&#…

【线程】线程池

线程池通过一个线程安全的阻塞任务队列加上一个或一个以上的线程实现&#xff0c;线程池中的线程可以从阻塞队列中获取任务进行任务处理&#xff0c;当线程都处于繁忙状态时可以将任务加入阻塞队列中&#xff0c;等到其它的线程空闲后进行处理。 线程池作用&#xff1a; 1.降…

Teams集成-订阅事件处理

在Teams会议侧边栏应用开发-会议转写-CSDN博客的基础上&#xff0c;使用/delta接口尝试获取实时转写&#xff0c;发现只能更新了一次&#xff0c;然后就不再更新了&#xff0c;想尝试使用订阅事件去获取转写&#xff0c;发现也不是实时的&#xff0c;当会议结束时&#xff0c;订…

排序题目:对角线遍历 II

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;对角线遍历 II 出处&#xff1a;1424. 对角线遍历 II 难度 6 级 题目描述 要求 给定一个二维整数数组 nums \texttt{nums} nums&#xff0c;将 …