DS二叉搜索树

前言

我们在数据结构初阶专栏已经对二叉树进行了介绍并用C语言做了实现,但是当时没有对二叉搜树进行介绍,而是把他放到数据结构进阶构专栏的第一期来介绍,原因是后面的map和set(红黑树)是基于搜索树的,这里介绍完后再去学习他们的成本会低一点!

本期内容介绍

二叉搜索树介绍

二叉搜索树的实现

二叉搜索树的应用

二叉搜索树的性能分析

二叉搜索树的介绍

什么是二叉搜索树?

二叉搜索树又称二叉排序树(走中序就是有序的)二叉查找树他是一颗空树或满足以下性质的二叉树!

1、如果它的左子树不为空,则左子树的所有节点的值都小于根节点的值

2、如果它的右子树不为空,则右子树的所有节点的值都大于根节点的值

3、它的左右子树也必须都为二叉搜索树

一般把二叉搜索树的节点的值叫做键值(key),一个键值唯一标识唯一一个节点!所以一般的key模型的二叉搜索树是不允许修改的(key_value模型仅可以修改value);因为在key模型的二叉搜索树中修改了key值会影响二叉搜索的搜索性,修改后可能就不在符合左边比根节点小,右边比根节点大的性质了!!!

二叉搜索树的实现

由于二叉搜索树是不允许修改键值(key)的,他主要作用是查询,所以没有修改接口

OK,还是和以前一样,先搭个架子出来:我们得有节点的类专门搞节点,然后一个二叉搜索树的类专门负责查找等操作!

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<K> Node;
public:private:Node* _root = nullptr;//只有一个成员,给一个缺省参数可以不用自己写构造了
};

下面就是实现插入、查询和删除的接口操作了!

insert

实现思路:

如果是第一次插入根节点为空则直接new一个值为key的节点连接到_root返回即可!

如果不是第一次插入,则从父节点开始查找合适的插入位置并用parent的变量记录合适位置的父节点的值,如果比当前的节点大去右边,否则去左边,直到为空找到了合适的位置,插入!如果是等于说明要插入的值引进存在直接返回

最后找到了插入位置new一个键值为key的节点连接到合适位置的父节点即可!

但是这里要插到做还是右呢?如果key的值比parent的值大插入到右边,否则插入到左边!

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;//去当前节点的右边}else{return false;//要插入的值已经存在}}//找到了要插入节点的合适位置cur = new Node(key);//申请一个键值为key的节点if (parent->_key < key)//key的值比父节点小{parent->_right = cur;//连接到父节点的右边}else{parent->_left = cur;//否则连接到父节点的左边}return true;//插入成功
}

OK,我们知道他的中序是有序的,所以我们可以插入一些乱序的数字然后走个中序看看是否是有序的即可验证师插入!

InOrder

实现思路:先遍历左子树 --> 根 -->右子树

但是这的根节点是BSTree私有的,咋办呢?解决方案右以下几种:

1、把你的测试函数搞成友元,就可以在测试函数中访问_root了(强烈不推荐)

2、提供get和set函数

3、把中序搞成私有的子函数,在提供一个共有的把子函数套一层(推荐)

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

OK,验证一下:

Find

实现思路:比较当前节点的值和key的值,如果比key大去右边,否则去左边找!

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;//没找到
}

Erase

实现思路

删除可以分为4种情况:1、被删节点的左为空   2、被删节点的右为空  3、被删节点的左右都为空  4、被删节点的左右都不为空。前三中只需要特殊处理一下可以合并为两种!注意:所有的删除都得找到这个节点,否则直接返回false;

被删节点的左为空

        1、如果当前节点是根节点且只有右单枝,让根节点指向它的右单枝的下一个

        2、不是根节点,如果被删节点==它的父节点的左,则把他的右连接到父亲的左

              如果被删节点==它的父节点的右,则把他的右连接到父亲的右

被删节点的右为空 

        1、如果当前节点是根节点且只有左单枝,让根节点指向它的左单枝的下一个

        2、不是根节点,如果被删节点==它的父节点的左,则把他的左连接到父亲的左

              如果被删节点==它的父节点的右,则把他的左连接到父亲的右

被删节点的左右都不为空

        用替换法删除即找一个合适的节点替他删除!可以找左子树最大(右)或 右子树最左         (小)的节点来替换。

        找到替换的节点后可以交换键值也可以赋值,然后删除替换节点即可!

bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key)//比key大,往左找{parent = cur;cur = cur->_left;}else if (cur->_key < key)//比key小,往右找{parent = cur;cur = cur->_right;}else//找到了{if (cur->_left == nullptr)//被删除节点的左为空{if (_root == cur)//删除根且只有右枝没有左枝{_root = cur->_right;//让根指向它的右}else{if (parent->_left == cur)//父亲的左==cur,把cur的右连接到父亲的左{parent->_left = cur->_right;}else//父亲的右==cur,把cur的右连接到父亲的右{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//被删除节点的右为空{if (_root == cur)//删除根且只有左枝没有右枝{_root = cur->_left;//让根指向它的左}else{if (parent->_left == cur)//父亲的左==cur,把cur的左连接到父亲的左{parent->_left = cur->_left;}else{parent->_right = cur->_left;//父亲的右==cur,把cur的右连接到父亲的左}}delete cur;}else//被删除节点的左右都不为空则用替换法删除即找到一个合适的节点来替他被删(左子树最(大)右,或右子树的最(小)左){Node* rightMinParent = cur;//右子树最(小)左节点的父亲Node* rightMin = cur->_right;//右子树的最左节点while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);//右子树左节点与被删节点的值交换if (rightMinParent->_left == rightMin)//如果rightMinParent的左==rightMin{rightMinParent->_left = rightMin->_right;//则把rightMin的右连接到rightMinParent的左}else//如果rightMinParent的右==rightMin{rightMinParent->_right = rightMin->_right;//则把rightMin的右连接到rightMinParent的右}delete rightMin;}return true;}}return false;
}

OK,验证一下:

析构函数

因为只有一个成员,所以直接给一个缺省值就不用写构造了!析构得写,和二叉树的销毁一样!先左子树销毁-->右子树销毁-->根销毁,可以和中序一样搞一个子函数外面套一下即可!

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

key模型的全部源码

#pragma oncetemplate<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<K> Node;
public: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;//去当前节点的右边}else{return false;//要插入的值已经存在}}//找到了要插入节点的合适位置cur = new Node(key);//申请一个键值为key的节点if (parent->_key < key)//key的值比父节点小{parent->_right = cur;//连接到父节点的右边}else{parent->_left = cur;//否则连接到父节点的左边}return true;//插入成功}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;//没找到}bool Erase(const K& key)
{Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key)//比key大,往左找{parent = cur;cur = cur->_left;}else if (cur->_key < key)//比key小,往右找{parent = cur;cur = cur->_right;}else//找到了{if (cur->_left == nullptr)//被删除节点的左为空{if (_root == cur)//删除根且只有右枝没有左枝{_root = cur->_right;//让根指向它的右}else{if (parent->_left == cur)//父亲的左==cur,把cur的右连接到父亲的左{parent->_left = cur->_right;}else//父亲的右==cur,把cur的右连接到父亲的右{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//被删除节点的右为空{if (_root == cur)//删除根且只有左枝没有右枝{_root = cur->_left;//让根指向它的左}else{if (parent->_left == cur)//父亲的左==cur,把cur的左连接到父亲的左{parent->_left = cur->_left;}else{parent->_right = cur->_left;//父亲的右==cur,把cur的右连接到父亲的左}}delete cur;}else//被删除节点的左右都不为空则用替换法删除即找到一个合适的节点来替他被删(左子树最(大)右,或右子树的最(小)左){Node* rightMinParent = cur;//右子树最(小)左节点的父亲Node* rightMin = cur->_right;//右子树的最左节点while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);//右子树左节点与被删节点的值交换if (rightMinParent->_left == rightMin)//如果rightMinParent的左==rightMin{rightMinParent->_left = rightMin->_right;//则把rightMin的右连接到rightMinParent的左}else//如果rightMinParent的右==rightMin{rightMinParent->_right = rightMin->_right;//则把rightMin的右连接到rightMinParent的右}delete rightMin;}return true;}}return false;
}void Inorder(){_Inorder(_root);cout << endl;}~BSTree(){Destory(_root);}private:void _Inorder(const Node* _root){if (_root == nullptr){return;}_Inorder(_root->_left);cout << _root->_key << " ";_Inorder(_root->_right);}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;}private:Node* _root = nullptr;//只有一个成员,给一个缺省参数可以不用自己写构造了
};

二叉搜索树的应用

1、K模型:就是我们上面介绍和实现的key模型,K模型中只存键值key,不可被修改!这个在生活中也是很常见的,比如说20万个单词找出拼写错误的单词!此时你只需要根据单词库中的单词建立一个key模型的搜索树即可,然后一一查找即可,如果是false的就是错误的!另外,宿舍的门禁系统,你刷脸或刷卡时会放你进去,这个也是一个key模型的搜索树!

2、KV模型:每个节点中不仅存一个键值key还要存一个key对应的value。即<KV>键值对。这个也是很常见的,比如说英汉词典,你的学号对应你等!

key_value模型全部源代码

上面K模型已经实现了,这里KV模型在上面的K上稍加修改即可(插入的时候多加一个value)!

namespace key_value
{template<class K, class V>struct BSTreeNode{BSTreeNode* _left;BSTreeNode* _right;K _key;V _value;BSTreeNode(const K& key, const V& value):_left(nullptr), _right(nullptr), _key(key),_value(value){}};template<class K, class V>class BSTree{typedef BSTreeNode<K, V> Node;public:bool insert(const K& key, const V& value){if (_root == nullptr)//第一次插入{_root = new Node(key, value);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;//去当前节点的右边}else{return false;//要插入的值已经存在}}//找到了要插入节点的合适位置cur = new Node(key, value);//申请一个键值为key的节点if (parent->_key < key)//key的值比父节点小{parent->_right = cur;//连接到父节点的右边}else{parent->_left = cur;//否则连接到父节点的左边}return true;//插入成功}Node* 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 cur;//找到了}}return cur;//没找到}bool Erase(const K& key){Node* parent = nullptr;Node* cur = _root;while (cur){if (cur->_key > key)//比key大,往左找{parent = cur;cur = cur->_left;}else if (cur->_key < key)//比key小,往右找{parent = cur;cur = cur->_right;}else//找到了{if (cur->_left == nullptr)//被删除节点的左为空{if (_root == cur)//删除根且只有右枝没有左枝{_root = cur->_right;//让根指向它的右}else{if (parent->_left == cur)//父亲的左==cur,把cur的右连接到父亲的左{parent->_left = cur->_right;}else//父亲的右==cur,把cur的右连接到父亲的右{parent->_right = cur->_right;}}delete cur;}else if (cur->_right == nullptr)//被删除节点的右为空{if (_root == cur)//删除根且只有左枝没有右枝{_root = cur->_left;//让根指向它的左}else{if (parent->_left == cur)//父亲的左==cur,把cur的左连接到父亲的左{parent->_left = cur->_left;}else{parent->_right = cur->_left;//父亲的右==cur,把cur的右连接到父亲的左}}delete cur;}else//被删除节点的左右都不为空则用替换法删除即找到一个合适的节点来替他被删(左子树最(大)右,或右子树的最(小)左){Node* rightMinParent = cur;//右子树最(小)左节点的父亲Node* rightMin = cur->_right;//右子树的最左节点while (rightMin->_left){rightMinParent = rightMin;rightMin = rightMin->_left;}swap(cur->_key, rightMin->_key);//右子树左节点与被删节点的值交换if (rightMinParent->_left == rightMin)//如果rightMinParent的左==rightMin{rightMinParent->_left = rightMin->_right;//则把rightMin的右连接到rightMinParent的左}else//如果rightMinParent的右==rightMin{rightMinParent->_right = rightMin->_right;//则把rightMin的右连接到rightMinParent的右}delete rightMin;}return true;}}return false;}void Inorder(){_Inorder(_root);cout << endl;}~BSTree(){Destory(_root);}private:void _Inorder(const Node* _root){if (_root == nullptr){return;}_Inorder(_root->_left);cout << _root->_key << " -> " << _root->_value << endl;_Inorder(_root->_right);}void Destory(Node*& root){if (root == nullptr)return;Destory(root->_left);Destory(root->_right);delete root;root = nullptr;}private:Node* _root = nullptr;//只有一个成员,给一个缺省参数可以不用自己写构造了};
}

可以一个类似于英汉单词的效果:

当然还可以统计某些东西的次数等!这里就不演示了!

二叉搜索树的性能分析

要执行插入和删除的操作的前提是得查找到相关的位置或元素,所以查找的效率代表了插入和删除的性能!我们来分析一下二叉搜索树的查找的时间复杂度:如果正常情况下是最多查找高度次所以时间复杂度是:O(logN)但是如果这个数是类似于链表的情况的话就是O(N)了!

这里你可能会担心,如果极端情况下真的退化成单链表那二叉搜索树的搜索性能就消失了,那该咋办呢?其实这个问题已经得到了解决!就是我们后面要介绍的AVL树和红黑树!马上后面会介绍的!

OK,好兄弟,本期分享就到这里,我们下期再见!

结束语:山高路远,看世界,也找自己。

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

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

相关文章

Shell脚本编写-定时清空文件内容,定时记录文件内容大小

find命令 – 根据路径和条件搜索指定文件 – Linux命令大全(手册)find命令的功能是根据给定的路径和条件查找相关文件或目录&#xff0c;其参数灵活方便&#xff0c;且支持正则表达式&#xff0c;结合管道符后能够实现更加复杂的功能&#xff0c;是Linux系统运维人员必须掌握的…

学习Rust的第29天: cat in Rust

今天即将是这个系列的最后一次内容&#xff0c;我们正在catRust 中从 GNU 核心实用程序进行重建。cat用于将文件内容打印到STDOUT.听起来很容易构建&#xff0c;所以让我们开始吧。 GitHub 存储库&#xff1a;GitHub - shafinmurani/gnu-core-utils-rust 伪代码 function read(…

省公派出国|社科类普通高校教师限期内赴英国访学交流

在国外访问学者申请中&#xff0c;人文社科类相对难度更大&#xff0c;尤其是英语语言学&#xff0c;作为非母语研究并不被国外高校看重。经过努力&#xff0c;最终我们帮助Z老师申请到英国坎特伯雷基督教会大学的访学职位&#xff0c;并在限期内出国。 Z老师背景&#xff1a; …

TypeScript学习日志-第二十天(模块解析)

模块解析 一、ES6之前的模块规范 前端模块化规范是有很多的&#xff0c;在es6模块化规范之前分别有一下的模块化规范 一、Commonjs 这是 NodeJs 里面的模块化规范 // 导入 require("xxx"); require("../xxx.js"); // 导出 exports.xxxxxx function() …

Java 【数据结构】常见排序算法实用详解(上) 插入排序/希尔排序/选择排序/堆排序【贤者的庇护】

登神长阶 上古神器-常见排序算法 插入排序/选择排序/堆排序 &#x1f4d4; 一.排序算法 &#x1f4d5;1.排序的概念 排序 &#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。 稳定性&a…

数据结构===树

文章目录 概要概念相关概念 有哪些常用的树小结 概要 树是一种新的数据结构&#xff0c;不同于数组&#xff0c;链表。就像大自然中的树&#xff0c;看下这个数据结构&#xff0c;很有意思&#xff0c;有一个主干&#xff0c;然后还有很多树叉&#xff0c;即支干。不错&#xf…

微信视频号如何变现呢,视频号涨粉最快方法

今天给大家带来的是视频号分成计划 视频号流量主这个项目&#xff0c;可以说这是目前的一个蓝海赛道&#xff0c;做的人也少&#xff0c;外面开的培训也很少&#xff0c;作为副业还是比较适合个人的&#xff0c;如果想批量操作这个项目&#xff0c;也比较适合工作室的。而且这…

51-48 CVPR 2024 | Vlogger: make your dream a vlog 自编剧制作视频博客

24年1月&#xff0c;上海交大、上海人工智能实验室、中科院联合发布Vlogger&#xff1a;make your dream a vlog。该论文主要工作是生成超过5分钟的视频博客vlog。鉴于现有文本到视频T2V生成方法很难处理复杂的故事情节和多样化的场景&#xff0c;本文提出了一个名为Vlogger的通…

Ansible的安装与基础命令的使用

Ansible Ansible 是一个开源的自动化工具&#xff0c;用于配置管理、应用部署和任务自动化。它由 Michael DeHaan 于 2012 年创建&#xff0c;后来被 Red Hat 收购。Ansible 的设计理念是简单易用&#xff0c;不需要在受管节点上安装任何代理软件&#xff0c;它通过 SSH&#…

WINDOWS配置IIS

1.安装IIS 1.1.打开启用Windows功能 打开“控制面板” > “程序和功能” > “启用或关闭 Windows 功能”。 1.2.启用IIS功能 打开“控制面板” > “程序和功能” > “启用或关闭 Windows 功能”。 勾选“Internet Information Services”&#xff0c;然后点击“确定…

vs配置cplex12.10

1.创建c空项目 2.修改运行环境 为release以及x64 3.创建cpp文件 4.鼠标右键点击项目中的属性 5.点击c/c&#xff0c;点击第一项常规&#xff0c;配置附加库目录 5.添加文件索引&#xff0c;主要用于把路径导进来 6.这一步要添加的目录与你安装的cplex的目录有关系 F:\program…

NFS共享存储服务配置实践

一、NFS 1.NFS定义 NFS&#xff08;Network File System&#xff09;网络文件服务&#xff1a;基于TCP/IP传输的网络文件系统协议&#xff0c;NFS服务的实现依赖于RPC&#xff08;Remote Process Call&#xff09;远端过程调用&#xff1a;通过使用NFS协议&#xff0c;客户机…

机器学习——3.梯度计算与梯度下降

基本概念 梯度的本意是一个向量&#xff08;矢量&#xff09;&#xff0c;表示某一函数在该点处的方向导数沿着该方向取得最大值&#xff0c;即函数在该点处沿着该方向&#xff08;此梯度的方向&#xff09;变化最快&#xff0c;变化率最大&#xff08;为该梯度的模&#xff0…

Vue、React实现excel导出功能(三种实现方式保姆级讲解)

第一种&#xff1a;后端返回文件流&#xff0c;前端转换并导出&#xff08;常用&#xff0c;通常公司都是用这种方式&#xff09; 第二种&#xff1a;纯后端导出&#xff08;需要了解&#xff09; 第三种&#xff1a;纯前端导出&#xff08;不建议使用&#xff0c;数据处理放…

【CTF MISC】XCTF GFSJ0512 give_you_flag Writeup(图像处理+QR Code识别)

give_you_flag 菜狗找到了文件中的彩蛋很开心&#xff0c;给菜猫发了个表情包 解法 图片的最后一帧好像闪过了什么东西。 用 Photoshop 打开&#xff0c;检查时间轴。 找到一张二维码&#xff0c;但是缺了三个角&#xff08;定位图案&#xff09;&#xff0c;无法识别。 找一…

从简单逻辑到复杂计算:感知机的进化与其在现代深度学习和人工智能中的应用(上)

文章目录 引言第一章&#xff1a;感知机是什么第二章&#xff1a;简单逻辑电路第三章&#xff1a;感知机的实现3.1 简单的与门实现3.2 导入权重和偏置3.3 使用权重和偏置的实现实现与门实现与非门和或门 文章文上下两节 从简单逻辑到复杂计算&#xff1a;感知机的进化与其在现代…

[开发|鸿蒙] 鸿蒙OS开发环境搭建(笔记,持续更新)

搭建开发环境流程&#xff1a; https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/installation_process-0000001071425528-V2 鸿蒙DevEco Studio 3.1.1 Release仅支持windows和mac系统 运行环境要求 为保证DevEco Studio正常运行&#xff0c;建议电脑配置…

16_Scala面向对象编程_函数

文章目录 1.声明Scala函数2.访问伴生对象3.空对象直接用的方法4.构造对象--通过object获取单例对象--直接new--scala独有apply()方式--scala有参构造--scala构造方法两大类使用辅构造如下上述代码主构造为辅助构造方法甚至可以多个多个辅助构造形参内容不能重不使用辅助构造和使…

[leetcode] 64. 最小路径和

文章目录 题目描述解题方法动态规划java代码复杂度分析 相似题目 题目描述 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#…

有什么好用的思维导图软件?6个软件教你快速进行思维导图的制作

有什么好用的思维导图软件&#xff1f;6个软件教你快速进行思维导图的制作 以下是六款常用且功能强大的思维导图软件&#xff0c;它们可以帮助您快速制作思维导图&#xff1a; 迅捷画图: 迅捷画图是一款在线思维导图工具&#xff0c;具有直观易用的界面和丰富的功能。用户可…