C++进阶——二叉搜索树BST

C++进阶——二叉搜索树BST

其实应该是二叉树内容的进阶版本:
二叉树在前面C数据结构阶段已经讲过,本节取名二叉树进阶是因为:

  1. map和set特性需要先铺垫二叉搜索树,而二叉搜索树也是一种树形结构
  2. 二叉搜索树的特性了解,有助于更好的理解map和set的特性
  3. 二叉树中部分面试题稍微有点难度,在前面讲解大家不容易接受,且时间长容易忘
  4. 有些OJ题使用C语言方式实现比较麻烦
    既然有了c++这么好的工具不如就再重新加强一下二叉搜索树(BST)。

二叉搜索树概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树
而且有一个特有意思的特点就是 中序输出是升序的。

举个例子:
在这里插入图片描述
int a [] = {5,3,4,1,7,8,2,6,0,9};

二叉搜索树的功能介绍

无非就是增、删、查、改,但是其中最难得的其实是删除。

1.查找

在这里插入图片描述
2.插入(增)
插入的具体过程如下:
一. 树为空,则直接插入

在这里插入图片描述
二.树不为空,按二叉搜索树性质查找插入位置,插入新节点
在这里插入图片描述

在这里插入图片描述
三.二叉树的删除

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

二叉树的实现

创建节点

	template<class T>
	struct BSTNode
	{
		BSTNode(const T& key = T()) //构造函数
			: _pLeft(nullptr), _pRight(nullptr), _key(key)
		{}

		BSTNode<T>* _pLeft;
		BSTNode<T>* _pRight;
		T _key;
	};

该节点需要有构造函数。

	template<class T>
	class BSTree
	{
	public:
		typedef BSTNode<T> node;
		typedef node* Pnode; 
		/*typedef BSTNode<T>* Pnode;*/
	private:
		Pnode _root = nullptr;
	};

每一个节点都需要一个root(node*类型)去调用。

中序打印

为了方便之后数据的检测,需要做一个输出的小工具,因为二叉树严格遵守中序输出是升序的规律,所以我们就设计一个中序打印。

	void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		
	void _InOrder(Pnode root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_pLeft);
			cout << root->_key << ' ';
			_InOrder(root->_pRight);
		}

由于我们平时提供这个接口时不会往里面输入参数,所以我们需要在封装一个函数帮我们输入参数,因此我们的封装了两个接口函数。

增删查改的实现

public:
		bool find(const T& key)
		{
			if (_root == nullptr)
				return false;
			Pnode cur = _root;
			while (cur)
			{
				if (cur->_key > key)
				{
					cur = cur->_pRight;
				}
				else if (cur->_key < key)
				{
					cur=cur->_pLeft;
				}
				else
				{
					return true;
				}
			}
			return false;
		}
		

查并不难,因为就是挨个遍历(因为二叉树的性质,左边比根小,右边比根大,这样便利可以减少很多时间的)
如果有就返回true,没有就返回false。

bool insert(const T& key)
		{
			if (_root == nullptr)
			{
				_root = new node(key);
				return true;
			}

			Pnode parent = nullptr;
			Pnode cur = _root;
			
			while (cur)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else
				{
					return false;
				}
			}
			cur=new node(key);//单独new结点却不进行连接是不行的 ,以下代码不能屏蔽
			if (parent->_key > cur->_key)
			{
				parent->_pLeft = cur;
			}
			else
			{
				parent->_pRight = cur;
			}
			return true;
		}

插入也很简单,也是一个一个查找合适的位置,当已经存在了就返回false,
当找到空的时候就可以插入了。

bool erase(const T& key)
		{
			if (_root == nullptr) return false;
			Pnode cur = _root;
			Pnode parent = cur;
			while (cur)
			{
				if (key > cur->_key)
				{
					parent = cur;
					cur = cur->_pRight;
				}
				else if (key < cur->_key)
				{
					parent = cur;
					cur = cur->_pLeft;
				}
				else
				{
					if (cur->_pLeft == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_pRight;
						}
						else
						{
							if (parent->_pLeft == cur)
							{
								parent->_pLeft = cur->_pRight;
							}
							else if (parent->_pRight == cur)
							{
								parent->_pRight = cur->_pRight;
							}
						}
						delete cur;
					}
					else if (cur->_pRight == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_pLeft;
						}
						else
						{
							if (parent->_pLeft == cur)
							{
								parent->_pLeft = cur->_pLeft;
							}
							else if (parent->_pRight == cur)
							{
								parent->_pRight = cur->_pLeft;
							}
						}
						delete cur;
					}
					else//左右两边都不为空
					{
						Pnode PRightMin = cur;
						Pnode RightMin = cur->_pRight;
						while (RightMin->_pLeft)
						{
							PRightMin = RightMin;
							RightMin = RightMin->_pLeft;
						}
						cur->_key = RightMin->_key;
						if (PRightMin->_pLeft == RightMin)
						{
							PRightMin->_pLeft = RightMin->_pRight;
						}
						else
						{
							PRightMin->_pRight = RightMin->_pRight;
						}
						delete RightMin;
					}
					return true;
				}
			}
			return false;
		}

删除就会难一点,因为要考察是否要移动子树。
首先你删除的如果是叶子节点,那直接删除就可以了不用考虑别的。
比如:
在这里插入图片描述
但是我要删除一个节点他不是叶子节点怎么办?
就需要考虑到领养机制了。(linux中的进程领养是不是很相似?其实也可以想象成一颗进程树)

单子树

当删除的节点在右边,而且有一颗右子树时:
如下图直接把右子树给父节点的右就可以了。
在这里插入图片描述
同理“左左”也是这个原理
在这里插入图片描述
左右和右左也是差不多的
因为只要是在父节点的左边就是比父节点小,在父节点的右边就是比父节点大。
在这里插入图片描述

双子树

最难的是双子树了该怎么办呢?
我只介绍一种方法(另一种类似),就是找到要删除节点的右树的最小节点(右子树的最左一个节点),然后让他替换掉要删除的节点,最后删除这个右树最小的节点。,但是当这个最小节点有右树怎么办呢?
就是把他的右树,托管给最小节点的父亲。

无右树
在这里插入图片描述
在这里插入图片描述

有右树
在这里插入图片描述
在这里插入图片描述

二叉搜索树的应用

  1. **K模型:**K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
    比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
    以单词集合中的每个单词作为key,构建一棵二叉搜索树
    在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

  2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生
    活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
    文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定
    单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对
    比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
    <单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key
    查询英文单词时,只需给出英文单词,就可快速找到与其对应的key

template<class K, class V>
struct BSTNode
 {
 BSTNode(const K& key = K(), const V& value = V())
 : _pLeft(nullptr) , _pRight(nullptr), _key(key), _Value(value)
 {}
 BSTNode<T>* _pLeft;
 BSTNode<T>* _pRight;
 K _key;
 V _value
 };
template<class K, class V>
class BSTree
 {
 typedef BSTNode<K, V> Node;
 typedef Node* PNode;
public:
 BSTree(): _pRoot(nullptr)
 {}
 // 同学们自己实现,与二叉树的销毁类似
 ~BSTree();
 // 根据二叉搜索树的性质查找:找到值为data的节点在二叉搜索树中的位置
 PNode Find(const K& key);
 bool Insert(const K& key, const V& value)
 {
 // ...
 
 PNode pCur = _pRoot;
 PNode pParent = nullptr;
 while (pCur)
 {
 pParent = pCur;
 if (key < pCur->_key)
 pCur = pCur->_pLeft;
 else if (key > pCur->_key)
 pCur = pCur->_pRight; // 元素已经在树中存在
 else
 return false;
 }
 // ...
 return true;
 }
 bool Erase(const K& key)
 {
 // ...
 return true;
 }
private:
 PNode _pRoot;
 };

二叉搜索树的性能分析

1.插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
2.对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的
深度的函数,即结点越深,则比较次数越多。但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
在这里插入图片描述
但是一个搜索二叉树退化成了一个单树枝树,那他的搜索价值就没有了,就完全成了一个链表,该如何改进呢?
就需要接下来讲的平衡二叉树(avl)和红黑树了。

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

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

相关文章

【路径规划】基于前向动态规划算法在地形上找到最佳路径(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

自然语言处理:词嵌入简介

动动发财的小手&#xff0c;点个赞吧&#xff01; Word Embeddings 机器学习模型“查看”数据的方式与我们&#xff08;人类&#xff09;的方式不同。例如&#xff0c;我们可以轻松理解“我看到一只猫”这一文本&#xff0c;但我们的模型却不能——它们需要特征向量。此类向量或…

C/C++每日一练(20230417)

目录 1. 字母异位词分组 &#x1f31f;&#x1f31f; 2. 计算右侧小于当前元素的个数 &#x1f31f;&#x1f31f;&#x1f31f; 3. 加一 &#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 J…

通过简单demo让你秒懂Python的编译和执行全过程

基本说明 python 是一种解释型的编程语言&#xff0c;所以不像编译型语言那样需要显式的编译过程。然而&#xff0c;在 Python 代码执行之前&#xff0c;它需要被解释器转换成字节码&#xff0c;这个过程就是 Python 的编译过程。 DEMO演示讲解 假设我们有以下 Python 代码&…

Session使用和原理分析图与实现原理-- 代码演示说明 Session 的生命周期和读取的机制代码分析

目录 Web 开发会话技术 -Session —session 技术 session 基本原理 Session 可以做什么 如何理解 Session Session 的基本使用 session 底层实现机制 原理分析图 代码演示 CreateSession.java 测试 Session 创的机制&#xff0c; 注意抓包分析​编辑 ReadSession.j…

python+vue 基于推荐算法的在线电影视播放网站

以广大影视剧迷们为研究对象&#xff0c;深入了解影视剧迷对在线视频观看视频的需求进行分析&#xff0c;形成系统需求分析设计一个符合影视剧迷们需求的在线视频网站。设计网站的前期工作包括对系统的各个功能进行详细分析&#xff0c;对数据库设计进行详细的描述&#xff0c;…

【C++】STL理解【容器】

【C】STL理解【容器】 1. STL概念引入 长久以来&#xff0c;软件界一直希望建立一种可重复利用的东西&#xff0c;以及一种得以制造出”可重复运用的东西”的方法&#xff0c;从函数(functions)&#xff0c;类别(classes),函数库(function libraries),类别库(class libraries…

nssctf web 入门(6)

这里通过nssctf的题单web安全入门来写&#xff0c;会按照题单详细解释每题。题单在NSSCTF中。 想入门ctfweb的可以看这个系列&#xff0c;之后会一直出这个题单的解析&#xff0c;题目一共有28题&#xff0c;打算写10篇。 目录 [SWPUCTF 2021 新生赛]caidao [SWPUCTF 2021 新…

GitLab与jekins结合构建持续集成(cl)环境(2)

目录 GItlab配置邮箱 绑定邮箱 创建群组 添加人员 创建一个项目 添加文件 新建分支 如何拉取代码 Git bash 演示 Git GUI演示 安装jenkins 更改插件镜像源 配置jenkins使用gitlab更新代码 安装jekins插件 配置jenkins免密拉取gatlab代码 jenkins创建项目 将代码…

VUE基本使用详解

目录 一、VUE框架原理 1. 了解VUE框架 2. VUE框架原理 3. MVC设计模式 4. MVVM设计模式 二、引入VUE框架 1. 本地引入 2. 网络引入 三、安装Vue插件 一、VUE框架原理 1. 了解VUE框架 vue 框架 是基于MVVM设计模式的前端框架&#xff0c;其中的Vue对象是MVVM设计模式中的VM视图…

Zebec Protocol 出席香港 Web3 峰会,带来了哪些信息?

梳理香港加密新政的细节&#xff0c;一个明确的脉络是&#xff0c;香港加密新政的整体目的是令虚拟资产交易明确化和合法化&#xff0c;通过不断完善的监管框架&#xff0c;促进香港虚拟资产行业的可持续和负责任地发展。 在加强合规和持牌经营的监管思路下&#xff0c;长期审慎…

TensorFlow 和 Keras 应用开发入门:1~4 全

原文&#xff1a;Beginning Application Development with TensorFlow and Keras 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 深度学习 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 不要担心自己的形…

《简化iOS APP上架流程,App Uploader助你搞定!》

转载&#xff1a;Appuploader常见问题 Appuploader 常见错误及解决方法 问题解决秘籍 遇到问题&#xff0c;第一个请登录苹果开发者官网 检查一遍账号是否有权限&#xff0c;是否被停用&#xff0c;是否过期&#xff0c;是否有协议需要同意&#xff0c;并且在右上角切换账号后…

页表结构详细说明

一、页表 1. 内存地址的分解 我们知道linux采用了分页机制&#xff0c;通常采用四级页表&#xff0c;页全局目录(PGD)&#xff0c;页上级目录(PUD)&#xff0c;页中间目录(PMD)&#xff0c;页表(PTE)。如下&#xff1a; 其含义定义在arch/arm64/include/asm/pgtable-hwdef.…

HCIP-6.9BGP路由反射器原理与配置

路由反射器原理与配置 1、路由反射器概念1.1、路由反射器原理&#xff1a;1.2、多集群路由反射器1.3、备份路由反射器2、路由反射器配置3、路由反射器防环机制 1、路由反射器概念 IBGP的水平分割&#xff0c;IBGP 1只能update一跳&#xff0c;就是说在IBGP 2 设备收到IBGP 1设…

密码学|DES加密算法|学习记录

DES简介 DES属于对称密码算法中的分组加密算法 密钥一共64bit&#xff0c;其中56位参与运算&#xff0c;其余8bit为校验位&#xff08;8 16 24 32 40 48 56 64&#xff09; n个64位明块经过加密后得到的n个64位密文块加在一起就是密文 DES一般步骤 IP置换 &#xff1a; IP置…

Python中的异常——概述和基本语法

Python中的异常——概述和基本语法 摘要&#xff1a;Python中的异常是指在程序运行时发生的错误情况&#xff0c;包括但不限于除数为0、访问未定义变量、数据类型错误等。异常处理机制是Python提供的一种解决这些错误的方法&#xff0c;我们可以使用try/except语句来捕获异常并…

AI已经解锁自动化能力 | 颠覆商业模式和劳动力市场

AI已经解锁自动化能力&#xff0c;将颠覆商业模式和劳动力市场。目前AutoGPT的开源项目&#xff1a; BabyAGI、Auto-GPT、AgentGPT、TeenagerAGI、Jarvis。 AutoGPT原理&#xff1a; 3个GPT4协同合作&#xff0c;一个GPT4负责分解目标创建任务&#xff0c;另一个GPT4负责分配…

C# switch case语句入门and业务必知点

具体的语法形式如下。 switch(表达式) { case 值 1: 语句块 1; break; case 值 2: 语句块 2; break; ... default: 语句块 n; break; } 在这里&#xff0c;switch 语句中表达式的结果必须是整型、字符串…

Linux用户的分类与家目录,ls、pwd、cd、mkdir、touch、rmdir、rm指令与选项等

Linux中用户的分类与用户的家目录 在Linux当中&#xff0c;用户的分类只分为两类&#xff0c;一类叫做超级用户root&#xff0c;还有就是其他也就是传说中的普通用户。我们刚刚登进去时&#xff0c;默认所处的目录是***/root或者/home/用户名***&#xff0c;比如说/root, /hom…
最新文章