C++:二叉搜索树

概念

二叉搜索树(BST - Binary Search Tree)是一种特殊的二叉树,每个顶点最多可以有两个子节点。其遵顼以下规则:

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

其有两个特性:

  1. 查找数据非常快,每次查找数据,只需要将数据与当前节点比较,然后决定去左子树还是右子树找,如果最后找到了nullptr,那就是不存在。
  2. 二叉搜索树的中序遍历,得到值是有序的。正是因为二叉搜索树左子树的值都小于根,右子树的值都大于根。中序遍历是 左子树 - 根 - 右子树,所以最后得到的数据就是有序的

 


模拟实现

节点类

template<class K>
struct BSTreeNode
{typedef BSTreeNode<K> Node;Node* _left;Node* _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 = nullptr;
};

 

插入

想要对二叉搜索树进行节点插入,有两种情况:

  1. 节点值存在:此时不再进行插入
  2. 节点值不存在:进行插入

值得注意的是:如果某一个值不存在于二叉搜索树中,那么插入这个值一定是在nullptr插入

bool Insert(const K& key)
{if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;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);if (key < parent->_key)parent->_left = cur;elseparent->_right = cur;return true;
}
中序遍历

中序遍历,对于二叉树而言是一个比较简单的操作,我们看到以下代码:

void InOrder(Node* root)
{if (root == nullptr)return;InOrder(root->_left);cout << root->_key << " - ";InOrder(root->_right);
}

以上代码,先遍历左子树InOrder(root->_left);,然后输出当前节点的值cout << root->_key << " - ";,再遍历右子树InOrder(root->_right);。是一个很常规的中序遍历,但是存在一个问题:这个函数外部没法传参。 

 比如说我要遍历某一个二叉搜索树bst

bst.InOrder(bst._root);

这个调用是存在问题的,那就是_root是私有成员,外部不能把_root作为参数传入中序遍历的接口。此时我们就需要再在外面套一层接口,像这样:

void InOrder()
{_InOrder(_root);
}void _InOrder(Node* root)
{if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " - ";_InOrder(root->_right);
}

我们给函数InOrder创建了一个子函数_InOrder,外部无需传参就可以调用InOrder。我们将中序遍历的代码写在了子函数中,而在InOrder内部,再去调用这个子函数 _InOrder(_root);,就可以正常传参了。 

 

查找

想要查找,基本逻辑就是:

当前节点的值小于key,到左子树查找
当前节点的值大于key,到右子树查找
当前节点的值等于key,返回true
如果查找到nullptr,说明不存在,返回false

bool Find(const K& key)
{Node* cur = _root;while (cur){if (key < cur->_key)cur = cur->_left;else if (key > cur->_key)cur = cur->_right;elsereturn true;}return false;
}

 

删除

首先,既然要删除特定的节点,那么我们就要先查找到该节点。既然要修改该节点,那么也要查找到其父节点,这个思路与前面的插入接口非常相似。      

只要被删除的节点,有一个子树为nullptr,那么就可以将另外一个子树直接链接到其父节点下。

这里还要额外考虑根节点的情况

if (cur->_left == nullptr)
{if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;
}
else if (cur->_right == nullptr)
{if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;
}
else
{//其他情况
}

当待删除节点的左右子树都不为空 

有两种解决方案:

  1. 取出左子树最大的值替换删除节点
  2. 取出右子树最小的值替换删除节点

取出节点后,我们把右子树最小节点rightMin的值交给待删除节点的cur,但是rightMin还有可能有右子树,那么就要把右子树移交给rightMin的父节点。

Node* rightMinParent = cur;
Node* rightMin = cur->_right;while (rightMin->_left)
{rightMinParent = rightMin;rightMin = rightMin->_left;
}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left == rightMin->_right;
elserightMinParent->_right == rightMin->_right;delete rightMin;

删除总代码:

bool Erase(const K& key)
{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{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMin == rightMinParent->_left)rightMinParent->_left = rightMin->_right;elserightMinParent->_right = rightMin->_right;delete rightMin;}return true;}}return false;
}

 

析构函数

想要删除整棵树,那就需要递归式地删除每一个节点,为了保证树的节点不会错乱,我们最好通过后序遍历删除,代码如下:

~BSTree()
{Destroy(_root);
}void Destroy(Node* root)
{if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;
}
拷贝构造

想要拷贝一棵树出来,我们也需要进行递归式的深拷贝,不过由于要先有父节点,再有子节点,所以要用前序遍历。代码如下:

BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}Node* Copy(Node* root)
{if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;
}
赋值重载

代码如下:

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

这是一个比较现代的赋值重载,注意我们在传参时BSTree<K> t不是一个引用,而是一个不同的参数,此时参数t是实参的一份拷贝,是通过拷贝构造创建的,然后我们把这个形参拷贝构造出来的树直接交换给自己: swap(_root, t._root);。这样就相当于通过拷贝构造,完成了一个赋值重载。 

 

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

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

递归删除
bool EraseR(const K& key)
{return _EraseR(_root, key);
}bool _EraseR(Node*& root, const K& key)
{if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_key){return _EraseR(root->_right, key);}else{//删除节点}
}

通过递归找到删除节点后,要考虑两种情况:

  1. 待删除节点有一个子树为nullptr
  2. 待删除节点拥有两个子树

先看到第一种:

Node* del = root;if (root->_left == nullptr)
{root = root->_right;
}
else if (root->_right == nullptr)
{root = root->_left;
}
else
{
}
delete del;
return true;

 接下来我们再看到左右子树都不为nullptr的情况:

 else
{Node* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);
}

总代码展示

BinarySearchTree.h

#pragma once
#include <iostream>
using namespace std;template<class K>
struct BSTreeNode
{typedef BSTreeNode<K> Node;Node* _root;Node* _left;Node* _right;K _key;BSTreeNode(const K& key):_root(nullptr),_left(nullptr),_right(nullptr),_key(key){}
};template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:BSTree() = default;BSTree(const BSTree<K>& t){_root = Copy(t._root);}Node* Copy(Node* root){if (root == nullptr)return nullptr;Node* newRoot = new Node(root->_key);newRoot->_left = Copy(root->_left);newRoot->_right = Copy(root->_right);return newRoot;}~BSTree(){Destroy(_root);}void Destroy(Node* root){if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;}BSTree<K>& operator=(BSTree<K> t){swap(_root, t._root);return *this;}bool Insert(const K& key){if (_root == nullptr){_root = new Node(key);return true;}Node* cur = _root;Node* parent = nullptr;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);if (key < parent->_key)parent->_left = cur;elseparent->_right = cur;return true;}bool Erase(const K& key){Node* cur = _root;Node* parent = nullptr;while (cur){if (key < cur->_key){parent = cur;cur = cur->_left;}else if (key > cur->_key){parent = cur;cur = cur->_right;}else{if (cur->_left == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_right;elseparent->_right = cur->_right;}delete cur;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_right;}else{if (cur == parent->_left)parent->_left = cur->_left;elseparent->_right = cur->_left;}delete cur;}else{Node* rightMinParent = cur;Node* rightMin = cur->_right;while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}cur->_key = rightMin->_key;if (rightMinParent->_left == rightMin)rightMinParent->_left == rightMin->_right;elserightMinParent->_right == rightMin->_right;delete rightMin;}return true;}}return false;}bool Find(const K& key){Node* cur = _root;while (cur){if (key < cur->_key)cur = cur->_left;else if (key > cur->_key)cur = cur->_right;elsereturn true;}return false;}void InOrder(){_InOrder(_root);cout << "end" << 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:void _InOrder(Node* root){if (root == nullptr)return;_InOrder(root->_left);cout << root->_key << " - ";_InOrder(root->_right);}bool _FindR(Node* root, const K& key){if (root == nullptr)return false;if (key < root->_key)return _FindR(root->_left, key);else if (key > root->_key)return _FindR(root->_right, key);elsereturn true;}bool _InsertR(Node*& root, const K& key){if (root == nullptr){root = new Node(key);return true;}if (key < root->_key)return _InsertR(root->_left, key);else if (key > root->_key)return _InsertR(root->_right, key);elsereturn false;}bool _EraseR(Node*& root, const K& key){if (root == nullptr)return false;if (key < root->_key){return _EraseR(root->_left, key);}else if (key > root->_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* rightMin = root->_right;while (rightMin->_left){rightMin = rightMin->_left;}swap(root->_key, rightMin->_key);return _EraseR(root->_right, key);}delete del;return true;}}Node* _root = nullptr;
};

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

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

相关文章

怎么将图片批量压缩处理?不牺牲图片清晰度的压缩秘诀

#北京city清凉walk指南# 夏日的北京&#xff0c;满目的绿色和清新空气让人沉醉。 然而&#xff0c;摄影爱好者们在记录这些美好瞬间的同时&#xff0c;也面临着大量图片的存储与管理难题。 随着手机和相机像素的提高&#xff0c;每张照片都可能成为存储空间的"大户&quo…

从0到1开发一个Vue3的新手引导组件(附带遇到的问题以及解决方式)

1. 前言: 新手引导组件,顾名思义,就是强制性的要求第一次使用的用户跟随引导使用应用,可以让一些第一次使用系统的新手快速上手,正好我最近也遇到了这个需求,于是就想着开发一个通用组件拿出来使用(写完之后才发现element就有,后悔了哈哈哈&#x1f62d;&#x1f62d;) 示例图…

Cygwin 安装、CMake 安装

1. cygwin Cygwin安装和验证 http://xiaxveliang.blog.163.com/blog/static/2970803420134693640905/ 如何完美安装 https://blog.csdn.net/u012778714/article/details/74012013 cygwin官网 https://cygwin.com/install.html 命令查询是否安装成功&#xff1a; Administra…

深入理解CSS中的块格式化上下文(BFC)

在Web开发中,CSS的布局机制是构建页面不可或缺的一部分。理解CSS中的各种布局概念对于创建响应式、整洁的网页至关重要。本文将深入探讨CSS中的一个重要概念——块格式化上下文(Block Formatting Context,简称BFC)。 1、什么是BFC? 块格式化上下文(BFC)是Web页面的可视…

【芯片方案】珠宝手机秤方案

珠宝手机秤作为一种便携式电子称重设备&#xff0c;因其小巧、便携、精度高等特点&#xff0c;广泛应用于各种需要精确称重的场景。可能这个目前在国内使用的人比较少&#xff0c;但在西方国家珠宝手机秤却是可以用来送礼的物品。因为珠宝手机秤的外观跟手机外观大多相似&#…

标准盒模型和怪异盒子模型的区别

在 CSS 中&#xff0c;标准盒模型和怪异盒模型是两种不同的盒子模型计算方式&#xff0c;主要区别如下&#xff1a; 一、标准盒模型&#xff08;content-box&#xff09; 1. 定义与组成 - 标准盒模型是 CSS 中默认的盒模型。 - 它由内容区域&#xff08;content&#xff09;、…

顶顶通呼叫中心中间件-打电话没声音检查步骤(mod_cti基于FreeSWITCH)

顶顶通呼叫中心中间件-电话没声音检查步骤(mod_cti基于FreeSWITH) 检查步骤 1、检查配置文件 检查配置文件&#xff1a;打开ccadmin -> 配置文件 -> vars -> external_ip$${local_ip_v4}看一下这个有没有配置正确的外网IP&#xff0c;如果没有配置正确就需要配置正…

PyCharm 2023.3.2 关闭时一直显示正在关闭项目

文章目录 一、问题描述二、问题原因三、解决方法 一、问题描述 PyCharm 2023.3.2 关闭时一直显示正在关闭项目 二、问题原因 因为PyCharm还没有加载完索引导致的 三、解决方法 方法一&#xff1a; 先使用任务管理器强制关闭&#xff0c;下次关闭时注意要等待PyCharm加载完索…

C语言-顺序表

&#x1f3af;引言 欢迎来到HanLop博客的C语言数据结构初阶系列。在这个系列中&#xff0c;我们将深入探讨各种基本的数据结构和算法&#xff0c;帮助您打下坚实的编程基础。本次我将为你讲解。顺序表&#xff08;也称为数组&#xff09;是一种线性表&#xff0c;因其简单易用…

ArcGIS Pro入门制图教程

地理信息系统 (GIS) 是一种使用地图显示和分析数据的方式。在本教程中&#xff0c;您将学习桌面 GIS 应用程序 ArcGIS Pro 的基础知识。 新加坡的一家旅行社希望制作一款宣传册&#xff0c;用于向游客介绍距离市中心热门目的地最近的火车站。该宣传册将与带有文本信息的地图相…

使用 `useAppConfig` :轻松管理应用配置

title: 使用 useAppConfig &#xff1a;轻松管理应用配置 date: 2024/7/11 updated: 2024/7/11 author: cmdragon excerpt: 摘要&#xff1a;本文介绍了Nuxt开发中useAppConfig的使用&#xff0c;它便于访问和管理应用配置&#xff0c;支持动态加载资源、环境配置切换、权限…

软设之观察者模式

设计模式中&#xff0c;观察者模式的意图是:定义对象间的一种一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。 比如说&#xff0c;有一个新闻网站&#xff0c;订阅的用户众多&#xff0c;假如说管理员发布了一…

使用Open3D处理点云数据:从读取到异常值剔除

在三维计算机视觉和机器人领域&#xff0c;点云数据是理解和分析环境的重要工具。Open3D是一个强大的开源库&#xff0c;它提供了丰富的功能来处理和分析点云数据。今天&#xff0c;我们将通过一段简单的代码示例&#xff0c;展示如何使用Open3D来读取点云文件、估计法线、并剔…

软考:软件设计师 — 2.操作系统

二. 操作系统 1. 操作系统概念 &#xff08;1&#xff09;操作系统的作用 操作系统是计算机硬件之上的第一层软件系统。 操作系统通常用来&#xff1a; 管理系统的硬件、软件、数据资源。控制程序运行。人机之间的接口。应用软件与硬件之间的接口。 可概括为&#xff1a; …

【Linux】内核文件系统系统调用流程摸索

内核层可以看到当前调用文件处理的进程ID 这个数据结构是非常大的&#xff1a; 我们打印的pid,tgid就是从这里来的&#xff0c;然后只需要找到pid_t的数据类型就好了。 下图这是运行的日志信息&#xff1a; 从上述日志&#xff0c;其实我也把write的系统调用加了入口的打印信…

开源项目的深潜:深掘机遇,直面挑战

在科技探索的深邃海洋中&#xff0c;开源项目不仅是一艘破浪前行的旗舰&#xff0c;更是深入未知领域、挖掘技术宝藏的潜水器。它们引领着技术创新的潮流&#xff0c;同时也直面着深海中的未知挑战。本文将深入剖析开源项目所蕴含的深层次机遇与必须克服的严峻挑战&#xff0c;…

CSS3实现彩色变形爱心动画【附源码】

随着前端技术的发展&#xff0c;CSS3 为我们提供了丰富的动画效果&#xff0c;使得网页设计更加生动和有趣。今天&#xff0c;我们将探讨如何使用 CSS3 实现一个彩色变形爱心加载动画特效。这种动画不仅美观&#xff0c;而且可以应用于各种网页元素&#xff0c;比如加载指示器或…

水库大坝安全监测险情主要内容

水库常见险情主要包括洪水漫顶、脱坡滑坡、坝体裂缝、 散浸、渗漏、漏洞、陷坑、管涌等&#xff0c;此外风浪冲击、水流冲刷等也会加剧险情的扩大。大坝险情万一抢护不及时&#xff0c;易导致发 生溃坝事故&#xff0c;造成极为严重的灾难性后果。要做到及时有效地 抢护大坝险情…

科技信息项目验收测试包括哪些内容?验收测试报告如何获取?

科技信息项目验收测试是指在科技信息项目完成后&#xff0c;组织专业测评团队对项目开发过程和成果进行全面、系统、客观的评测和鉴定的过程。通过验收测试&#xff0c;可以评估项目的质量、功能完整性以及满足业务需求的程度&#xff0c;并为项目的成功上线提供依据。 为了进…

uniapp实现table排序

根据后端接口传来的数字大小对列表进行升序/降序展示 效果图&#xff0c;价格由高到低降序 价格由低到高 升序 js 降序升序代码如下 export default {data() {return {MtList:[]}},onLoad() {this.MtypeName();//加载列表方法},methods: {MtypeName(){//列表方法this.$api.…