【C++】学习笔记——二叉搜索树

文章目录

  • 十四、二叉搜索树
    • 1. 二叉搜索树的概念
    • 2. 二叉搜索树的实现
      • 查找
      • 插入
      • 中序遍历
      • 删除
      • 拷贝构造
      • 析构函数
      • 赋值重载
      • 完整代码
    • 3. 二叉搜索树的应用
      • K搜索模型
      • KV搜索模型
  • 未完待续


十四、二叉搜索树

1. 二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:

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

在这里插入图片描述

二叉搜索树的查找非常方便,最多查找 树的高度 次就能找到,他还有一个隐藏特点:二叉搜索树的中序遍历是有序的。

2. 二叉搜索树的实现

二叉搜索树,树是一种结构,需要用类来定义,节点也是一种结构,需要另一个类来定义。节点对数来说是完全公开的,所以节点我们使用 struct 。我们创建一个头文件:BinarySearchTree.h ,在里面先编写一个框架出来:

#pragma once// struct BinarySearchTreeNode
// 节点结构体
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)
{}
};//class BinarySearchTree
// 二叉搜索树类
template<class K>
class BSTree // 采用缩写
{typedef BSTreeNode<K> Node;
public://
private:Node* _root = nullptr;
};

查找

二叉搜索树非常适合查找,逻辑也非常简单。通过不断与当前节点比较而选择进入左子树或者右子树。

bool Find(const K& key)
{Node* cur = _root;// cur处有节点while (cur){// 要查找的值比当前节点的值要小if (key < cur->_key){// 前往左子树cur = cur->_left;}//要查找的值比当前节点的值要大else if (key > cur->_key){// 前往右子树cur = cur->_right;}// 相等时else{return true;}}// 没找到return false;
}

在这里插入图片描述

插入

插入也是比较简单的,当插入的关键字已存在时,现阶段没有多大用处,我们就插入已有的关键字,当关键字不存在时,也是一种查找,当 找到空位置 时,该位置就可以插入。唯一需要注意的是,需要使用一个指针指向插入位置的父节点,否则无法进行节点之间的连接。

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;}else{// 新节点插在右子树parent->_right = cur;}return true;
}

中序遍历

中序遍历需要借助当前节点,所以需要一个形参,但是根节点又是属于类的私有成员变量,在外部无法访问,所以我们需要嵌套一层函数来访问。

// private内
void _InOrder(Node* root)
{if (root == nullptr){return;}_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);
}// 套一层函数来获取根节点
// public内
void InOrder()
{_InOrder(_root);std::cout << std::endl;
}

我们来测试一下:创建一个源文件 Test.cpp

#include <iostream>
#include "BinarySearchTree.h"
using namespace std;int main(void)
{BSTree<int> t;int a[] = { 8,3,1,10,6,4,7,14,13 };for (auto e : a){t.Insert(e);}t.InOrder();return 0;
}

在这里插入图片描述
二叉搜索树的中序遍历确实是有序的,看来前面的函数都没有问题。

删除

前面的函数其实都是比较简单的,二叉搜索树的删除会比较困难。二叉搜索树的删除分为三种情况:

  1. 删除没有孩子节点的节点。
    在这里插入图片描述
    比如删除上图中的 1 4 7 13 。这种情况最简单,直接删掉即可。
  2. 删除只有一个孩子的节点。
    在这里插入图片描述
    如上图中的 10 14 。这种情况也算是比较简单的,比如说我们要删除 14 。我们只需要将 14 的孩子 托付给 14 的父节点 即可。
    在这里插入图片描述
  3. 删除有两个孩子的节点。


比如删除上图中的 8 3 6 。这种情况最为复杂,也最难。这种情况该怎么删除呢?有一种方法叫做 替换删除法 ,比如说我要删除 3 ,我可以从 3 的孩子里找出能够替换掉 3 的节点,即 左子树中的最右侧(最大)的节点,或者右子树中的最左侧(最小)的节点 。这两个节点与被删除的节点替换都可以完成删除操作。
在这里插入图片描述

在这里插入图片描述

其实情况1和情况2差不多,因为情况1同样可以理解为将 空托付给父节点

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 == parent->_left){parent->_left = cur->_right;}else{parent->_right = cur->_right;}delete cur;return true;}// 右子树为空,托付左子树else if (cur->_right == nullptr){if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}delete cur;return true;}// 都不为空使用替换删除法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;}else{rightMinParent->_right = rightMin->_right;}delete rightMin;return true;}}}return false;
}

上面的函数有两个小细节,①假如删除8,则其右孩子 10 就是要删除的节点,所以 rightMinParent 不能初始化为 nullptr。②假如删除8,则其右孩子 10 就是要删除的节点,此时 108 的右节点,所以 rightMin 不一定是 rightMinParent 的左节点。
测试一下:

#include <iostream>
#include "BinarySearchTree.h"
using namespace std;int main(void)
{BSTree<int> t;int a[] = { 8,3,1,10,6,4,7,14,13 };for (auto e : a){t.Insert(e);}t.InOrder();t.Erase(8);t.InOrder();return 0;
}

在这里插入图片描述
已经没有问题了吗?NoNoNo,当要删除的节点是根节点,并且只有左子树或者只有右子树时就会出问题了。此时 Parentnullptr 但是被解引用了,所以我们需要加一下判断:

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;}else{parent->_right = cur->_right;}}delete cur;return true;}// 右子树为空,托付左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;}// 都不为空使用替换删除法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;}else{rightMinParent->_right = rightMin->_right;}delete rightMin;return true;}}}return false;
}

拷贝构造

由于默认生成的拷贝构造函数是浅拷贝,直接拷贝构造会导致两个对象指向同一个二叉搜索树,我们要实现深拷贝所以要手动写一个拷贝构造函数。

// 强制生成默认构造函数
BSTree() = default;// 拷贝构造函数
BSTree(const BSTree<K>& t)
{_root = Copy(t._root);
}// private内
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);
}// private内
void Destroy(Node* root)
{if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;
}

测试一下:

#include <iostream>
#include "BinarySearchTree.h"
using namespace std;int main(void)
{BSTree<int> t;int a[] = { 8,3,1,10,6,4,7,14,13 };for (auto e : a){t.Insert(e);}t.InOrder();BSTree<int> t1(t);t1.InOrder();return 0;
}

在这里插入图片描述

赋值重载

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

完整代码

#pragma once// struct BinarySearchTreeNode
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){}
};//class BinarySearchTree
template<class K>
class BSTree
{typedef BSTreeNode<K> Node;
public:// 强制生成默认构造函数BSTree() = default;// 拷贝构造函数BSTree(const BSTree<K>& t){_root = Copy(t._root);}BSTree<K>& operator=(BSTree<K> t){std::swap(_root, t._root);return *this;}// 析构函数~BSTree(){Destroy(_root);}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;}else{// 新节点插在右子树parent->_right = cur;}return true;}bool Find(const K& key){Node* cur = _root;// cur处有节点while (cur){// 要查找的值比当前节点的值要小if (key < cur->_key){// 前往左子树cur = cur->_left;}//要查找的值比当前节点的值要大else if (key > cur->_key){// 前往右子树cur = cur->_right;}// 相等时else{return true;}}// 没找到return false;}void InOrder(){_InOrder(_root);std::cout << std::endl;}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;}else{parent->_right = cur->_right;}}delete cur;return true;}// 右子树为空,托付左子树else if (cur->_right == nullptr){if (cur == _root){_root = cur->_left;}else{if (cur == parent->_left){parent->_left = cur->_left;}else{parent->_right = cur->_left;}}delete cur;return true;}// 都不为空使用替换删除法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;}else{rightMinParent->_right = rightMin->_right;}delete rightMin;return true;}}}return false;}
private: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;}void _InOrder(Node* root){if (root == nullptr){return;}_InOrder(root->_left);std::cout << root->_key << " ";_InOrder(root->_right);}void Destroy(Node* root){if (root == nullptr){return;}Destroy(root->_left);Destroy(root->_right);delete root;}Node* _root = nullptr;
};

3. 二叉搜索树的应用

K搜索模型

K搜索模型主要运用于想要 快速找到某个值在不在 。比如说门禁系统。

KV搜索模型

KV搜索模型主要运用于想要 通过某个值(key)查找另一个值(value)在不在 。比如说商场车库,通过车牌号找到进车库的时间好用来计费。

上面实现的就是 K搜索模型 ,如何实现 KV搜索模型 呢?其实也不难,节点结构体内多存入一个 value 即可,然后输入 key 的时候也要输入 value。逻辑与 K搜索模型 大差不差。


未完待续

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

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

相关文章

同旺科技 FLUKE ADPT 隔离版发布 ---- 2

所需设备&#xff1a; 1、FLUKE ADPT 隔离版 内附链接&#xff1b; 应用于&#xff1a;福禄克Fluke 12E / 15BMax / 17B Max / 101 / 106 / 107 应用于&#xff1a;福禄克Fluke 15B / 17B / 18B 正面&#xff1a; 反面&#xff1a; 侧面&#xff1a; 开孔位置&#xff08;可…

面试八股之MySQL篇4——事务篇

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

react组件中的共享数据

在前面的示例中&#xff0c;每个 MyButton 都有自己独立的 count&#xff0c;当每个按钮被点击时&#xff0c;只有被点击按钮的 count 才会发生改变&#xff1a; 然而&#xff0c;你经常需要组件 共享数据并一起更新。 为了使得 MyButton 组件显示相同的 count 并一起更新&…

四川邮电职院领导访知了汇智,深化AI专业教育与行业对接

5月16日&#xff0c;四川邮电职业技术学院信息工程学院软件教研室的领导团队莅临我司&#xff0c;就人工智能专业建设进行深入的交流与学习。我司总经理、副总经理等高层领导亲自接待&#xff0c;对学院领导一行的到来表示了热烈的欢迎&#xff0c;并全程陪同参观了公司的核心区…

【技术实操】中标麒麟高级服务器操作系统实例分享,rsync数据同步配置方案

1.rsync介绍 rsync是一款开源的、快速的、多功能的、可实现全量及增量的本地或远程数据同步备份工具。 在守护进程模式&#xff08;daemon mode&#xff09;下&#xff0c;rsync默认监听TCP端口873&#xff0c;以原生rsync传输协议或者通过远程shell如RSH或者SSH提供文件。SS…

基于BERT的中文情感分析实战

数据与代码链接见文末 bert开源项目解读:谷歌开源项目BERT源码解读与应用实例-CSDN博客 基于BERT的中文命名实体识别识别实战:基于BERT的中文命名实体识别识别实战-CSDN博客 1.数据 在data目录下,提供了中文情感分析的数据, 类别1代表正向情感,类别2代表负向情感。

金融信贷风控基础知识

一、所谓风控(What && Why) 所谓风控&#xff0c;可以拆解从2个方面看&#xff0c;即 风险和控制 风险(what) 风险 这里狭隘的特指互联网产品中存在的风险点&#xff0c;例如 账户风险 垃圾注册账号账号被泄露盗用 交易支付风险 刷单&#xff1a;为提升卖家店铺人气…

DETR原理分析

TransformerDetection&#xff1a;引入视觉领域的首创DETR 论文名称&#xff1a;End-to-End Object Detection with Transformers 论文地址&#xff1a;https://arxiv.org/abs/2005.12872 重要的图要经常出现&#xff0c;下图就是&#xff1a; DETR原理分析 网络架构部分解读…

UNI-APP设置屏幕保持常亮-不熄灭屏幕

前言 最近在实际开发过程中&#xff0c;我们会发现在自己使用的app当中会根据系统无操作熄灭屏幕对于一下需要长时间保持屏幕的业务就很不友好&#xff0c;uni-app也是提供了相应方法加上代码之后-注意app端没报错-不生效就是权限问题-需要设置相对应权限-打自定义包 代码实现…

【BSP开发经验】用户态栈回溯技术

前言 在内核中有一个非常好用的函数dump_stack, 该函数在我们调试内核的过程中可以打印出函数调用关系&#xff0c;该函数可以帮助我们进行内核调试&#xff0c;以及让我们了解内核的调用关系。同时当内核发生崩溃的时候就会自己将自己的调用栈输出到串口。 栈回溯非常有利于我…

溪谷联运SDK功能全面解析

近期&#xff0c;备受用户关注的手游联运10.0.0版本上线了&#xff0c;不少用户也选择了版本更新&#xff0c;其中也再次迎来了SDK的更新。溪谷软件和大家一起盘点一下溪谷SDK的功能都有哪些吧。 一、溪谷SDK具有完整的运营功能和高度扩展性 1.登录&#xff1a;登录是SDK最基础…

物体检测算法-R-CNN,SSD,YOLO

物体检测算法-R-CNN&#xff0c;SSD&#xff0c;YOLO 1 R-CNN2 SSD3 Yolo总结 1 R-CNN R-CNN&#xff08;Region-based Convolutional Neural Network&#xff09;是一种基于区域的卷积神经网络&#xff0c;是第一个成功将深度学习应用到目标检测上的算法。它主要由三个步骤组…

LeetCode 131题详解:高效分割回文串的递归与动态规划方法

❤️❤️❤️ 欢迎来到我的博客。希望您能在这里找到既有价值又有趣的内容&#xff0c;和我一起探索、学习和成长。欢迎评论区畅所欲言、享受知识的乐趣&#xff01; 推荐&#xff1a;数据分析螺丝钉的首页 格物致知 终身学习 期待您的关注 导航&#xff1a; LeetCode解锁100…

关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

前情概要 这个问题其实困扰了我一周时间&#xff0c;一周都在 Google 上旅游&#xff0c;我要如何动态的设置 RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢&#xff1f;经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。 当然在此…

为什么我用save保存更新,数据库不更新,反而新增一条

今天发现一个奇怪的问题&#xff1a; 为什么我用save保存更新的数据后&#xff0c;数据库不更新&#xff0c;但是增加了一条空数据&#xff0c;我的前台也把数据用json传上去了&#xff0c;也成功了&#xff0c;但是数据库没有更新相应行的数据&#xff0c;而是新增了一条数据&…

实现顺序表各种基本运算的算法

实验一&#xff1a;实现顺序表各种基本运算的算法 一、实验目的与要求 目的: 领会顺序表存储结构和掌握顺序表中各种基本运算算法设计。 内容: 编写一个程序sqlist.cpp,实现顺序表的各种基本运算和整体建表算法(假设顺序表的元素类型ElemType为char),并在此基础上设计一个…

计组期末必考大题

一.寻址方式详解 1.直接寻址 指令地址码直接给到操作数所在的存储单元地址 2.间接寻址 A为操作数EA的地址 3.寄存寻址 4.寄存器间接寻址 5.变址寻址 6.基地址寻址 7.小结 二、指令周期详解 一、基本概念 指令周期:去除指令并执行指令所需要的时间指令周期:由若干个CPU周…

C++/ cuda kernel中的模版元编程识别 kernel 模版的数据类型

1&#xff0c;模版元编程 模板元编程是一种利用 C 模板系统在编译时进行计算和生成代码的技术。其原理基于模板特化、递归、模板参数推导等特性&#xff0c;通过模板实例化和展开&#xff0c;在编译时生成代码&#xff0c;以实现在编译期间进行复杂计算和代码生成的目的。 2&am…

前端笔记-day07

学成在线网站 文章目录 效果图代码展示index.htmlindex.cssbase.css 效果图 代码展示 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…

键盘盲打是练出来的

键盘盲打是练出来的&#xff0c;那该如何练习呢&#xff1f;很简单&#xff0c;看着屏幕提示跟着练。屏幕上哪里有提示呢&#xff1f;请看我的截屏&#xff1a; 截屏下方有8个带字母的方块按钮&#xff0c;这个就是提示&#xff0c;也就是我们常说的8个基准键位&#xff0c;我…