C++两种方法实现二叉搜索树

文章目录

  • 1.二叉搜索树(BST Binary Search Tree)
    • 1.1二叉搜索树的概念
    • 2.2二叉搜索树的操作
      • 2.2.1二叉搜索树的查找
      • 2.2.2二叉搜索树的插入
      • 2.2.3二叉搜索树的删除
  • 2.3二叉搜索树的实现
    • 2.3.1二叉搜索的基本结构
    • 2.3.2查找节点
    • 2.3.3插入节点
    • 2.3.4删除节点
        • 删除度为1的节点
        • 删除度为2的节点
      • 实现
        • 中序遍历
        • 删除节点的最终版本
  • 二叉搜索树(递归实现)
    • 递归查找节点
    • 递归插入函数
    • 递归删除节点
  • 全代码

1.二叉搜索树(BST Binary Search Tree)

1.1二叉搜索树的概念

二叉搜索树又称二叉排序树,它具有以下特点:

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

2.2二叉搜索树的操作

二叉搜索树

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

2.2.1二叉搜索树的查找

a.从根节点出发,如果要找的值大于当前的节点值,去右边找,
如果要找的值小于当前的节点值,则去左边找,
如果相等则表示找到了。
b.如果值存在的话,最多查找高度次

2.2.2二叉搜索树的插入

a.如果树为空,则直接新增节点,赋值root指针
b.树不为空,按二叉搜索树性质查找插入位置,插入新节点

2.2.3二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

2.3二叉搜索树的实现

2.3.1二叉搜索的基本结构

利用C++的模板实现泛型编程。

template <class K>
struct BSTNode
{BSTNode(const K& key = K()):_left(nullptr),_right(nullptr),_key(key){}BSTNode<K>* _left;BSTNode<K>* _right;K _key;
};
template <class K>
class BSTree
{typedef BSTNode<K> Node;typedef Node* pNode;
public:BSTree()//构造:_root(nullptr){}~BSTree()//销毁{}
private:pNode _root;
};

2.3.2查找节点

a.从根节点出发,如果要找的值大于当前的节点值,去右边找,
如果要找的值小于当前的节点值,则去左边找,
如果相等则表示找到了。
b.如果值存在的话,最多查找高度次

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

2.3.3插入节点

a.如果树为空,则直接新增节点,赋值root指针
b.树不为空,按二叉搜索树性质查找插入位置,插入新节点

//插入bool Insert(const K& key = K()){if (!_root){_root = new Node(key);return true;}pNode cur = _root;pNode parent = nullptr;//引入父节点的目的是为了方便在后续,找到插入位置的父节点。如果没有父节点我们将无法插入while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (key > parent->_key){parent->_right = cur;}else if (key < parent->_key){parent->_left = cur;}return true;}

父节点的引入,方便找到插入位置的父节点。最后的if判断是为了确定插入的位置。

2.3.4删除节点

要删除节点我们首先要查找节点的位置。但我们查找成功时,此节点可能存在3种状态:
1.度为0的节点
2.度为1的节点
3.度为2的节点
那么这三种情况我们要如何删除呢?度为0我们可以直接删除,如果直接删除度为1的节点,那么就会造成节点丢失了。

删除度为1的节点

当我们要删除14时,我们可以先把14的父节点10的右指针指向13,然后再把14删除。
删除14

删除度为2的节点

当我们删除3的时候,要怎么办呢?直接删除显然不行,如果让父节点来指向它的左右节点也不现实,毕竟有两个孩子嘛。
还记得堆的删除吗?当我们要删除堆顶元素时,我们会把堆顶元素和堆底元素的值调换再删除堆底元素,最后在进行向下调整。
我们也可以利用这个思想,我把3和4交换。为什么一定要和4交换呢?首先4在3的右边便确定了它一定会大于3。大于3的话肯定会大于3的左子树。然后又因为它在3右子树的左边,便又确定了它一定会小于3的右子树。最后我们在删除交换后的3节点就可以了。这种做法既可以删除3节点又不会影响二叉搜索树的规则。
删除3

实现

初版

//删除bool erase(const K& key = K()){pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//找到了if (cur->_left == nullptr)//度为0或者度为1,因为两种删除方法可以重和。所以写在一起{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;return true;}else if (cur->_right == nullptr){if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_right;}delete cur;return true;}else//度为2的节点{pNode leftnode = cur->_right;while (leftnode->_left)//找到最左边的节点{leftnode = leftnode->_left;}cur->_key = leftnode->_key;//因为leftnode为要删除的位置,所以可以直接给cur赋值delete leftnode;return true;}}}return false;}

下面来看看效果,那么我们要实现一下中序遍历

中序遍历
void Print(){_Print(_root);cout << endl;}
private:void _Print(pNode root){if (root == nullptr){return;}_Print(root->_left);cout << root->_key << " ";_Print(root->_right);}

因为打印不需要传参数,但是递归需要传参数。那么我们就可以在类里面在建立一个新的子函数,将子函数放在private里面避免在外被访问。
下面我们来看看插入效果
插入结果
然后我们再看看删除效果
删除效果
出错了!可以看出是删除度为2时出的问题。
为什么会这样呢?原因就是我们指向了野指针,直接删除leftnode时,其父节点的指针会指向野指针。除此之外还有一个问题,如果我们删除的是根节点,此时我们的parent节点指向空,在后序便会出问题。为了处理这个问题我们要在度为1和2都加上一个判断

删除节点的最终版本
//删除bool erase(const K& key = K()){pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//找到了if (cur->_left == nullptr)//度为0或者度为1,因为两种删除方法可以重和。所以写在一起{if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else {if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_right;}}delete cur;return true;}else//度为2的节点{pNode NodeParent = cur;pNode leftnode = cur->_right;while (leftnode->_left)//找到最左边的节点{NodeParent = leftnode;leftnode = leftnode->_left;}cur->_key = leftnode->_key;//因为leftnode为要删除的位置,所以可以直接给cur赋值if (leftnode == NodeParent->_left)NodeParent->_left = leftnode->_right;elseNodeParent->_right = leftnode->_right;delete leftnode;return true;}}}return false;}

二叉搜索树(递归实现)

写完迭代的代码,下面我们来看看,如何利用递归实现二叉搜索树

递归查找节点

bool FindR(const K&key){return _FindR(_root, key);}private:bool _FindR(pNode root, const K& key){if (!root) return false;if (key > root->_key){return _FindR(root->_right, key);}else if (key < root->_key){return _FindR(root->_left, key);}else{return true;}}

递归插入函数

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

pNode& root下面来展开讲讲为什么,这里用引用。我们都知道,引用是一个变量的别名,在这里就是表示上一个节点的左右指针的别名。那么当其左右指针为空时,我们可以直接new一个新的节点,再让其指向它。也就是相当于正常迭代中父节点的作用了。

递归删除节点

bool eraseR(const K& key){return _eraseR(_root, key);}private:bool _eraseR(pNode& root, const K& key){if (!root) return false;if (key > root->_key){return _eraseR(root->_right, key);}else if (key < root->_key){return _eraseR(root->_left, key);}else{pNode tmp = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{pNode leftnode = root->_right;while (leftnode->_left){leftnode = leftnodeo->_left;}swap(root->_key, leftnode->_key);return _eraseR(root->_right, key);}delete tmp;return true;}}
					while (leftnode->_left){leftnode = leftnodeo->_left;}swap(root->_key, leftnode->_key);return _eraseR(root->_right, key);

此次调用_eraseR的目的,把当前节点的右子树传给函数,再此时调用的函数中,利用其内部的删除度为1和0的功能把节点删除。算的上是一种功能的复用了。

运行结果
删除根节点没问题,那么大概就没有问题了。

全代码

#include <iostream>
using namespace std;
template <class K>
struct BSTNode
{BSTNode(const K& key = K()):_left(nullptr),_right(nullptr),_key(key){}BSTNode<K>* _left;BSTNode<K>* _right;K _key;
};
template <class K>
class BSTree
{typedef BSTNode<K> Node;typedef Node* pNode;
public:BSTree()//构造:_root(nullptr){}~BSTree()//销毁{}pNode Find(const K& key){if (!_root) return nullptr;pNode cur = _root;while (cur){if (key > cur->_key){cur = cur->_right;}else if (key < cur->_key){cur = cur->_left;}else{return cur;}}return nullptr;}//插入bool Insert(const K& key = K()){if (!_root){_root = new Node(key);return true;}pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(key);if (key > parent->_key){parent->_right = cur;}else if (key < parent->_key){parent->_left = cur;}return true;}//删除bool erase(const K& key = K()){pNode cur = _root;pNode parent = nullptr;while (cur){if (key > cur->_key){parent = cur;cur = cur->_right;}else if (key < cur->_key){parent = cur;cur = cur->_left;}else{//找到了if (cur->_left == nullptr)//度为0或者度为1,因为两种删除方法可以重和。所以写在一起{if (cur == _root){_root = cur->_right;}else{if (parent->_left == cur){parent->_left = cur->_right;}else{parent->_right = cur->_right;}}delete cur;return true;}else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else {if (parent->_left == cur){parent->_left = cur->_left;}else{parent->_right = cur->_right;}}delete cur;return true;}else//度为2的节点{pNode NodeParent = cur;pNode leftnode = cur->_right;while (leftnode->_left)//找到最左边的节点{NodeParent = leftnode;leftnode = leftnode->_left;}cur->_key = leftnode->_key;//因为leftnode为要删除的位置,所以可以直接给cur赋值if (leftnode == NodeParent->_left)NodeParent->_left = leftnode->_right;elseNodeParent->_right = leftnode->_right;delete leftnode;return true;}}}return false;}//打印,中序递归遍历void Print(){_Print(_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(pNode& root, const K& key){if (!root) return false;if (key > root->_key){return _eraseR(root->_right, key);}else if (key < root->_key){return _eraseR(root->_left, key);}else{pNode tmp = root;if (root->_left == nullptr){root = root->_right;}else if (root->_right == nullptr){root = root->_left;}else{pNode leftnode = root->_right;while (leftnode->_left){leftnode = leftnode->_left;}swap(root->_key, leftnode->_key);return _eraseR(root->_right, key);}delete tmp;return true;}}bool _InsertR(pNode& root, const K& key){if (!root){root = new Node(key);return true;}if (key > root->_key){return _InsertR(root->_right, key);}else if (key < root->_key){return _InsertR(root->_left, key);}else{return false;}}bool _FindR(pNode root, const K& key){if (!root) return false;if (key > root->_key){return _FindR(root->_right, key);}else if (key < root->_key){return _FindR(root->_left, key);}else{return true;}}private:void _Print(pNode root){if (root == nullptr){return;}_Print(root->_left);cout << root->_key << " ";_Print(root->_right);}private:pNode _root;
};

在这里插入图片描述

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

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

相关文章

百度网站收录提交入口

百度网站收录提交入口 在网站刚建立或者更新内容后&#xff0c;及时将网站提交给搜索引擎是提高网站曝光和获取流量的重要步骤之一。百度作为中国最大的搜索引擎之一&#xff0c;网站在百度中的收录情况尤为重要。下面介绍一下如何通过百度的网站收录提交入口提交网站。 1. 百…

卷积神经网络(CNN)的数学原理解析

文章目录 前言 1、介绍 2、数字图像的数据结构 3、卷积 4、Valid 和 Same 卷积 5、步幅卷积 6、过渡到三维 7、卷积层 8、连接剪枝和参数共享 9、卷积反向传播 10、池化层 11、池化层反向传播 前言 本篇主要分享卷积神经网络&#xff08;CNN&#xff09;的数学原理解析&#xf…

Git:修改的时候在自己的分支修改,如果在其它分支修改代码,会无法切换回自己的分支

如果已经发生了: 首先在当前分支把没问题的代码提交一下&#xff0c; 保存当前修改&#xff1a;在当前分支上进行一次提交&#xff0c;将当前修改保存起来。&#xff08;但是不要推送到远程仓库&#xff09; 切换回自己的分支&#xff1a;尝试切换回自己的分支。如果报错&…

新model开发记录

模型使用 -- 用blender导出为 fbx &#xff0c;修改渲染方式&#xff08;点击模型->Materials->Extract Materials(将材质从fbx中 单独提取出来了)->Materials 选择 Shader -> SimpleURPToonLitExample 点开脸的材质&#xff0c;勾选第一条&#xff09; 解决角色…

深入了解C语言中的结构体类型与内存对齐

引言&#xff1a; 在C语言中&#xff0c;结构体是一种自定义的数据类型&#xff0c;它允许我们将不同类型的数据组合在一起&#xff0c;形成一个新的数据类型。结构体的使用为我们解决了一些复杂数据的表示和处理问题&#xff0c;不仅限于单单的整型或者字符。本文将深入探讨结…

甘特图进度跟踪与风险预警机制

项目管理就像一场跋涉,甘特图则是我们的路线图和指南针。制定好行程计划后,我们就要跟着它一路前行,时刻核对进度,防患于未然。 一张优秀的甘特图不仅绘制了任务的起止时间,更重要的是它将各项工序的逻辑关系和制约条件清晰可见。我们遵循这个时间线前进,就能确保万无一失。通…

你以为BBA电车不行?看看宝马财报里电车有多挣钱

作者 |许行知 编辑 |德新 如果放在中国市场百花齐放、争奇斗艳的电动车里&#xff0c;宝马的电动车乍看确实平平无奇。 但从全球市场来看&#xff0c;电动车早已成为宝马重要的利润增长点。 3月末&#xff0c;宝马集团举办线上沟通会&#xff0c;总结了其2023年的业绩表现&a…

Rustdesk如何编译代码实现,客户端只有控制权限,而且只能控制指定ID,不能控制其他ID

环境&#xff1a; RustDesk1.1.9 自建服务器 问题描述&#xff1a; Rustdesk如何编译代码实现&#xff0c;客户端只有控制权限&#xff0c;而且只能控制指定ID,不能控制其他ID 解决方案&#xff1a; 详细方案&#xff0c;有需要私聊

【Java面试题系列】基础篇

目录 基本常识标识符的命名规则八种基本数据类型的大小&#xff0c;以及他们的封装类3*0.10.3返回值是什么short s1 1; s1 s1 1;有什么错? short s1 1; s1 1;有什么错?简述&&与&的区别&#xff1f;简述break与continue、return的区别&#xff1f;Arrays类的…

Vue-Electron配置及踩坑

前言 大道至简。太复杂的教程不看。 本篇将记述我创建好Vue3项目之后&#xff0c;用Electron把页面呈现出来的整个过程。会记录一些踩坑。 首先&#xff0c;Electron官网可以参考。但是它只是作出了一个普通的html结构该如何用Electron呈现出来&#xff0c;vue的配置有一些变…

HarmonyOS 应用开发之分布式数据对象跨设备数据同步

场景介绍 传统方式下&#xff0c;设备之间的数据同步&#xff0c;需要开发者完成消息处理逻辑&#xff0c;包括&#xff1a;建立通信链接、消息收发处理、错误重试、数据冲突解决等操作&#xff0c;工作量非常大。而且设备越多&#xff0c;调试复杂度也将同步增加。 其实设备…

k8s集群pod和node状态监控

1.安装 kube-state-metrics 1.1下载yaml文件 下载的文件统一放到目录 &#xff1a; /opt curl -L -O https://raw.githubusercontent.com/gjeanmart/kauri-content/master/spring-boot-simple/k8s/kube-state-metrics.yml 1.2修改配置文件 修改namespace为dev&#xff08;def…