二叉搜索树(Binary Seach Tree)模拟实现

news/2024/5/20 14:21:34/文章来源:https://blog.csdn.net/m0_73904148/article/details/130988238

目录

二叉搜索树的性质

二叉搜索树的实现 

结点类

接口类(BSTree)

 二叉搜索树的插入(insert)

二叉搜索树的查找(find) 

 二叉搜索树删除(erase)

第二种、删除的结点右子树为空

第三种、删除的结点左子树为空

第四种、删除的结点左右都不为空

实现

二叉搜索树模拟实现代码总结 

二叉搜索树的缺陷

缺陷

解决办法:


二叉搜索树的性质

二叉搜索树正如其名,底层是一个二叉树

二叉搜索树的每一颗左子树小于根节点的值

二叉搜索树的每一颗右子树大于根节点的值

二叉搜索树的结点中的值不能重复!

如图所示,即为一颗二叉搜索树

也正因为以上两条的性质,那么我们在二叉搜索树中进行查找时,时间复杂度是高度次

如下是在一颗二叉搜索树中查找元素6的过程

 二叉搜索树的中序遍历完后就是有序序列

如图中序遍历为:6,20,25,30,40,50,60 

二叉搜索树的实现 

结点类

二叉搜索树我们使用三叉链来实现

三叉链:需要一个父指针,左孩子指针,右孩子指针

template<class T>
struct BSTreeNode
{BSTreeNode<T>* _left;BSTreeNode<T>* _right;BSTreeNode<T>* _parent;T _data;BSTreeNode(const T& data):_data(data),_left(nullptr),_right(nullptr),_parent(nullptr){}
};

接口类(BSTree)

 二叉搜索树的接口类和list的接口类差不多,

list存储的是一个头节点,二叉搜索树存储的是一个根节点

template<class T>
class BSTree
{typedef BSTreeNode<T> Node;public://接口实现private://给个缺省参数为空,后续就不用写构造函数了Node* _root = nullptr;
};

 二叉搜索树的插入(insert)

二叉搜索树的插入,只要结点中的值不发生重复,那么就一定是插入到树的叶子结点

 如图

如果我们插入一个1,则在结点6的左边,返回true

如果我们插入一个23,则在结点25的左边,返回true

但如果我们插入一个30,与结点中的值重复则不插入,返回false

我们先看看插入一个23的过程图

 但上面这个过程中有一个问题,那就是cur最终会走到空,但空指针没办法访问

如果我们不记录cur的父节点的话,最终当cur走到空时,无法判断它的父节点是谁,最终无法链接

所以我们在插入时还需要有一个parent指针,那么代码实现如下:

bool insert(const T& x)
{Node* cur = _root;//用来遍历的结点Node* parent = nullptr;//记录cur的父节点while(cur){    //_data 是Node的值if(x > cur->_data){parent = cur;cur = cur->_right;}else if(x < cur->_data){parent = cur;cur = cur->_left;}else{//走到这里就说明x == cur->_data//要插入的值已经在树中存在return false;}}//创建结点Node* ret = new Node(x);//把结点插入树中if (cur == _root){//特殊情况,当树中一个结点都没,那么cur不会进入循环//此时parent为空_root = ret;return true;}if(ret->_data > parent->_data){parent->_right = ret;}else{parent->_left = ret;}ret->_parent = parent;return true;
}

二叉搜索树的查找(find) 

Node* find(const T& x)
{Node* cur = _root;while(cur){if(cur->_data < x){//查找的值比当前结点的值要大cur = cur->_right;}else if(cur->_data > x){//查找的值比当前结点的值要小cur = cur->_left;}else{//找到了return cur;}}//cur为空,树里面没有这个值 return nullptr;
}

 二叉搜索树删除(erase)

二叉搜索树的删除就稍微复杂一点,一共分为3种情况

        这一种情况很简单,我们只需改变其父节点指向删除结点的指针为空,再释放删除结点即可

   注意:当树中只有一个结点时它父节点为空,如果我们访问父节点就会报错,所以我们要针对树中只有一个结点的情况特殊处理

bool EraseZero(Node* cur)
{if (cur == _root){//这里专门处理只有一个根节点的情况delete cur;_root = nullptr;return true;}Node* parent = cur->_parent;if(parent->_left == cur){parent->_left = nullptr;}else{parent->_right = nullptr;}delete cur;return true;
}

第二种、删除的结点右子树为空

如图所示

假设我们要删除图上的值为60的结点

第一步:先找到这个结点

第二步:找到这个结点的父亲结点和左孩子结点,并记录下来

第三步:删除这个结点

第四步:让这个左孩子结点顶替这个结点的父亲结点的右孩子位置

注意:如图这种情况下,删除值为30的结点,但这个结点是根节点没有父亲结点,所以对这种情况特殊处理一下

第三种、删除的结点左子树为空

左子树为空的处理方法可以归并为右子树为空的情况,简直一摸一样

那么我们就可以写一个接口把这两种情况都囊盖了

bool EraseOne(Node* cur)
{if(_root == cur){Node* ret = cur->_left == nullptr ? cur->_right : cur->_left;delete cur;_root = ret;return true;}Node* parent = cur->_parent;Node* ret = cur->_left == nullptr ? cur->_right : cur->_left;if(parent->_left == cur){parent->_left = ret;}else{parent->_right = ret;}ret->_parent = parent;delete cur;return true;
}

第四种、删除的结点左右都不为空

这一种情况当我们在进行删除时需要找到这个结点的替代结点

替代结点一定有至少一个孩子结点为空

当我们替换完以后此时的二叉搜索树必须还保持着它的性质

替换节点有两种选择

第一种、删除结点的左孩子的最右结点

第二种、删除结点的右孩子的最左结点

如图,假如我们要删除值为30的结点

 替换结点为红圈圈起来的两个

 我们实现的话就采用第一种选用替换结点的方式

当我们替换完以后如图

 我们就把删除左右子树都不为空的情况转化为了左右子树都为空的情况

我们再来看一张图,此时如果我们要删除25:红圈圈起来的是替换节点

  

 交换删除结点与替换结点

可以看到,此时就由删除一个左右子树都不为空的结点变成了删除至少有一颗树为空的结点

bool EraseTwo(Node* cur)
{//1、找到替代结点Node* tmp = cur->_left;while(tmp->_right){tmp = tmp->_right;}std::swap(cur,tmp);if(cur->_left == nullptr && cur->_right == nullptr){//复用情况1return EraseZero(cur);}else{//复用情况2、3return EraseOne(cur);}
}

实现

bool erase(const T& x)
{if(!find(x)){//删除的结点不存在return false;}Node* cur = _root;while(cur){if(cur->_data > x){cur = cur->_left;}else if(cur->_data < x){cur = cur->_right;}else{//找到了要删除的结点break;}}//理论上cur此时一定是break出来的,那么到这里我们就开始分情况讨论了if(cur->_left == nullptr && cur->_right == nullptr){return EraseZero(cur);}if(cur->_left && cur->_right){return EraseTwo(cur);}else if(cur->_left != nullptr || cur->_right != nullptr){return EraseOne(cur)}
}

二叉搜索树模拟实现代码总结 

template<class T>
struct BSTreeNode
{BSTreeNode<T>* _left;BSTreeNode<T>* _right;BSTreeNode<T>* _parent;T _data;BSTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class T>
class BSTree
{typedef BSTreeNode<T> Node;
public://接口实现bool insert(const T& x){Node* cur = _root;//用来遍历的结点Node* parent = nullptr;//记录cur的父节点while (cur){//_data 是Node的值if (x > cur->_data){parent = cur;cur = cur->_right;}else if (x < cur->_data){parent = cur;cur = cur->_left;}else{//走到这里就说明x == cur->_data//要插入的值已经在树中存在return false;}}//创建结点Node* ret = new Node(x);//把结点插入树中if (cur == _root){//特殊情况,当树中一个结点都没,那么cur不会进入循环//此时parent为空_root = ret;return true;}if (ret->_data > parent->_data){parent->_right = ret;}else{parent->_left = ret;}ret->_parent = parent;return true;}Node* find(const T& x){Node* cur = _root;while (cur){if (cur->_data < x){//查找的值比当前结点的值要大cur = cur->_right;}else if (cur->_data > x){//查找的值比当前结点的值要小cur = cur->_left;}else{//找到了return cur;}}//cur为空,树里面没有这个值 return nullptr;}bool erase(const T& x){Node* cur = _root;while (cur){if (cur->_data > x){cur = cur->_left;}else if (cur->_data < x){cur = cur->_right;}else{//找到了要删除的结点break;}}//到这里我们就开始分情况讨论了if (cur == nullptr){//没有这个结点return false;}if (cur->_left == nullptr && cur->_right == nullptr){return EraseZero(cur);}else if (cur->_left && cur->_right){return EraseTwo(cur);}else{return EraseOne(cur);}}void Print(){_Print(_root);std::cout << std::endl;}
private:void _Print(Node* cur){if (cur == nullptr){return;}_Print(cur->_left);std::cout << cur->_data << " ";_Print(cur->_right);}bool EraseZero(Node* cur){if (cur == _root){//这里专门处理只有一个根节点的情况delete cur;_root = nullptr;return true;}Node* parent = cur->_parent;if (parent->_left == cur){parent->_left = nullptr;}else{parent->_right = nullptr;}delete cur;return true;}bool EraseOne(Node* cur){if (_root == cur){Node* ret = cur->_left == nullptr ? cur->_right : cur->_left;delete cur;_root = ret;return true;}Node* parent = cur->_parent;Node* ret = cur->_left == nullptr ? cur->_right : cur->_left;if (parent->_left == cur){parent->_left = ret;}else{parent->_right = ret;}ret->_parent = parent;delete cur;return true;}bool EraseTwo(Node* cur){//1、找到替代结点Node* tmp = cur->_left;while (tmp->_right){tmp = tmp->_right;}std::swap(cur, tmp);if (cur->_left == nullptr && cur->_right == nullptr){//复用情况1return EraseZero(cur);}else{//复用情况2、3return EraseOne(cur);}}
private://给个缺省参数为空,后续就不用写构造函数了Node* _root = nullptr;
};

二叉搜索树的缺陷

缺陷

二叉搜索树在普通情况下没有什么明显的缺陷

不过在极端情况下二叉搜索树效率会降低(甚至退化成链表)

如图:

 这个二叉搜索树可以看到,有5个结点,却有三层,我们都知道二叉搜索树的效率是高度次

那么如果我们再极端一点,插入一个有序数组,此时查找的时间复杂度就来到了O(n)

如图为插入一个有序数组(1,2,3,4,5,6,7,8)后的二叉搜索树

 可以看到,此时二叉搜索树就退化成了链表

解决办法:

下一期我们会介绍一个数据结构叫做(AVL树)也叫做高度平衡的二叉搜索树,它就完美解决了如上的问题

那么这一期就到这了,感谢大家的支持

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

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

相关文章

【算法】手写题

文章目录 画一个三角形实现三栏布局通过position和margin通过float和margin通过flex实现 变量提升题实现边框0.5px深拷贝快速排序 画一个三角形 .box1 {width: 0;height: 0;border: 10px solid;border-color: red transparent transparent transparent;}实现三栏布局 三栏布局…

深入浅出之Docker Compose详解

目录 1.Docker Compose概述 1.1 Docker Compose 定义 1.2 Docker Compose产生背景 1.3 Docker Compose 核心概念 1.4 Docker Compose 使用步骤 1.5 Docker Compose 常用命令 2. Docker Compose 实战 2.1 Docker Compose下载和卸载 2.2 Docker Compose 项目概述 2.3 Do…

呈现视觉妙技:使用Python将MP4视频转化为迷人的GIF图像

前言 GIF图片对于我来说是一个很好的展示方式&#xff0c;GIF 图片能够展示动态的图像效果&#xff0c;对于展示计算机视觉算法或结果非常有用。例如&#xff0c;我可以使用 GIF 图片来展示运动跟踪、姿势识别、图像分割、目标检测等任务的结果&#xff0c;以更生动和直观的方…

20230606夏新(Amoi)的4K显示器D320B2000的亮点检测

20230606夏新&#xff08;Amoi&#xff09;的4K显示器D320B2000的亮点检测 2023/6/7 0:14 https://item.jd.com/63690000655.html 夏新&#xff08;Amoi&#xff09;电脑显示器高清家用办公电竞吃鸡游戏液晶监控直播大屏便携显示屏幕 32英寸【直面 4k/144hz双模式 全面屏】黑 …

总结891

学习目标&#xff1a; 月目标&#xff1a;6月&#xff08;线性代数强化9讲1遍&#xff0c;背诵15篇短文&#xff0c;考研核心词过三遍&#xff09; 周目标&#xff1a;线性代数强化1讲&#xff0c;英语背3篇文章并回诵&#xff0c;检测 每日必复习&#xff08;5分钟&#xff…

Day_40关于图的总结

一. 实际问题的抽象与建模 如果我们需要研究一个实际问题&#xff0c;首先第一步就是对这个实际问题进行抽象&#xff0c;抽象是从众多的事物中抽取出共同的、本质性的特征&#xff0c;而舍弃其非本质的特征的过程。具体地说&#xff0c;抽象就是人们在实践的基础上&#xff0c…

chatgpt赋能python:Python如何自动换行

Python如何自动换行 在Python编程中&#xff0c;有时候我们需要输出很长的文本或字符串&#xff0c;这时候就需要自动换行的功能。本文将介绍Python中实现自动换行的几种方法。 方法一&#xff1a;使用字符拼接 在Python中&#xff0c;我们可以使用"“来拼接字符串。如…

Internal error. Please report to https://jb.gg/ide/critical-startup-errors

大佬的解决方式&#xff1a;PyCharm 2023 启动报错的处理 部分同学&#xff0c;发现在安装 PyCharm 2023.1.2 以及 PyCharm 2023.2 的抢先体验版之后&#xff0c;运行的时候愣是直接弹出了类似上面的报错。 反正&#xff0c;别慌&#xff01; 是的&#xff0c;他们有 bug。 …

【Java】深入理解Java虚拟机 | 垃圾收集器GC

《深入理解Java虚拟机》的阅读笔记——第三章 垃圾收集器与内存分配策略。 参考了JavaGuide网站的相关内容&#xff1a;https://javaguide.cn/ Q&#xff1a;哪些内存需要回收&#xff1f;什么时候回收&#xff1f;如何回收&#xff1f; 2 对象已死吗&#xff1f; 2.1 引用…

剪映自动打关键帧

牙叔教程 简单易懂 这是给单张图片打关键帧的教程, 给图片打关键帧有四个步骤 鼠标点选图片打起始帧跳转到图片末尾打结束帧 打帧是一件很费手的事情, 所以我写了个自动化的代码, 专门用来打关键帧, 使用的软件是 AutoHotkey 关键帧参数的详细解释 剪映 自动打关键帧 AutoH…

Azure Log Analytics:与Power BI集成

注&#xff1a;本文最初发布于https://d-bi.gitee.io, 2023年6月迁移至CSDN 前述 Azure Log Analytics是Azure Monitor中的一项分析服务。本文将讲述通过Log Analytics与Power BI集成的方式&#xff0c;获取Power BI工作区内的日志信息&#xff0c;包括各PBI数据集的CPU消耗&a…

Web安全总结

目录 网站架构一般web服务器结构相比于传统的网络攻击&#xff0c;基于web的攻击有什么不同&#xff1f;HTTP协议HTTP响应拆分攻击HTTPS针对HTTPS协议的攻击那么如何保证证书的唯一性&#xff1f; HTTP会话Cookie和Session的关系HTTP会话攻击解决方案 Web访问中的隐私问题Web应…

chatgpt赋能python:Python如何空一行:介绍

Python如何空一行&#xff1a;介绍 在Python编程中&#xff0c;经常需要在输出文字或代码时进行空行分隔。一个常用的场景就是在代码中加入注释&#xff0c;将注释与代码分开&#xff0c;使代码逻辑更加清晰易懂。在某些情况下&#xff0c;也需要在输出文字时进行空行分割&…

ChatGPT-Plugins-Searchable

ChatGPT Plus 用户应该都知道Plus已经开放了插件功能&#xff0c;但是在插件商店里存在一个较大的问题插件数量超过100款&#xff0c;却没有便捷的搜索功能。 而我们在查找一款插件时&#xff0c;需要从插件商店的第一页点击到最后一页一个个找&#xff0c;显然这非常的麻烦。 …

JUC基础-0606

9.ReentrantReadWriteLock读写锁 9.1 锁的基本概念 悲观锁&#xff1a;不支持并发&#xff0c;效率低&#xff0c;但是可以解决所有并发安全问题 乐观锁&#xff1a;支持并发读&#xff0c;维护一个版本号&#xff0c;写的时候比较版本号进行控制&#xff0c;先提交的版本号…

【Vue】三:Vue组件: 组件使用和组件嵌套

文章目录 1.第一个组件1.1不使用组件前1.2创建组件1.3注册组件1.4使用组件1.5 细节 2.组件嵌套 1.第一个组件 1.1不使用组件前 1.2创建组件 Vue.extends({该配置项和new Vue的配置项几乎相同})区别&#xff1a; &#xff08;1&#xff09;创建Vue组件的时候&#xff0c;不能使…

Kubernetes之pod

Kubernetes之pod 在通过docker运行程序时&#xff0c;我们通常会制作Dockerfile文件构建镜像。也可以基于某个镜像运行容器在容器中安装组件之后&#xff0c;再基于容器生成镜像 使用如下命令可生成镜像&#xff0c;想了解更多参数请添加–help docker build -f Dockerfile路…

【Leetcode -138.复制带随机指针的链表 -2130.链表最大孪生和】

Leetcode Leetcode -138.复制带随机指针的链表Leetcode -2130.链表最大孪生和 Leetcode -138.复制带随机指针的链表 题目&#xff1a;给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构…

4种普遍的机器学习分类算法

朴素贝叶斯分类 朴素贝叶斯分类是基于贝叶斯定理与特征条件独立假设的分类方法&#xff0c;发源于古典数学理论&#xff0c;拥有稳定的数学基础和分类效率。它是一种十分简单的分类算法&#xff0c;当然简单并不一定不好用。通过对给出的待分类项求解各项类别的出现概率大小&a…

企业应该如何选择适合自己的直播平台?

企业应该如何选择适合自己的直播平台&#xff1f;本文将从功能需求、可靠性与稳定性、用户体验、技术能与售后服务能力等方面进行综合考虑&#xff0c;帮助您做出明智的决策&#xff0c;或是说提供选型方面的参考。 企业在选择一家直播平台时应考虑以下因素&#xff1a; 1. 企…