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

news2024/12/23 12:16:23

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.coloradmin.cn/o/446946.html

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

相关文章

十七、WLAN概述

文章目录 前言一、WLAN 网络演化过程二、IEEE 802.11主要标准三、WLAN 解决方案四、模拟器登录AC1、配置AC2、配置云3、登录 前言 无线局域网WLAN&#xff08;Wireless Local Area Network&#xff09;是一种利用无线技术实现主机等终端设备灵活接入以太网的技术&#xff0c;它…

hadoop集群部署常见问题解决

1、权限 •Permission denied&#xff08;权限被拒绝&#xff09; Hadoop的运行日志在$HADOOP_HOME/logs内 也可以查看日志排错 只要出现Permission denied就是权限问题 hadoop安装文件夹或/data文件夹&#xff0c;未被授权给hadoop用户&#xff0c;所以无权限操作 2、环境变…

在金融领域使用机器学习的 9个技巧

机器学习已经倍证明可以预测结果和发掘隐藏的数据模式。但是必须小心使用&#xff0c;并遵循一些规则&#xff0c;否则就会在数据的荒野中徘徊而无所获。使用机器学习进行交易的道路充满了陷阱和挑战&#xff0c;只有那些勤奋认真地遵循规则的人才能从中获得收益。下面是一些技…

如何建立到NAS中新增容器的ssh连接

注&#xff1a;首先需按照教程建立Zerotier连接&#xff0c;然后进入新建的nginx镜像&#xff0c;为root用户建立密码。 查看容器类型 Debian 系镜像: cat /etc/issue Redhat 系镜像: cat /etc/redhat-release Alpine 系镜像: cat /etc/os-release 安装并启动ssh apt-get …

SHELL环境变量和引用

目录 1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。 a.安装邮件服务 b.创建脚本对要求进行设计 c.编辑配置文件 ​编辑d.做计划任务 ​编辑 e.进行测试 2、判断web服务是否运行…

POLARDB 从一个使用者的角度来说说,POALRDB 怎么打败 MYSQL RDS

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请联系 liuaustin3 &#xff0c;在新加的朋友会分到2群&#xff08;共…

记录-实现深拷贝的四种方式

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 概念介绍 深拷贝&#xff1a;在堆内存中重新开辟一个存储空间&#xff0c;完全克隆一个一模一样的对象 浅拷贝&#xff1a;不在堆内存中重新开辟空间&#xff0c;只复制栈内存中的引用地址。本质上两个…

Python双向循环链表的操作

目录 一、双向循环链表 双向循环链表图 二、双向循环链表的操作 1、判断链表是否为空 2&#xff0c;链表长度 3&#xff0c;遍历整个链表 4&#xff0c;在链表头部添加元素 5、链表尾部添加元素 6&#xff0c;在指定位置插入元素 7&#xff0c;修改指定位置的元素 8&a…

被裁后找不到工作,本质上是因为原来的能力就配不上高薪,如果技术好,根本不怕被裁,相当于白送n+1!...

被裁员后&#xff0c;能要求公司补缴公积金吗&#xff1f; 一位网友问&#xff1a; 被裁员了&#xff0c;要求公司把历史公积金全部足额缴纳&#xff0c;现在月薪2.3万&#xff0c;但公司每个月只给自己缴纳300元公积金&#xff0c;结果一次补了二十多万&#xff0c;一次性取出…

进程等待、进程替换

目录 进程等待 waitpid函数 wait函数 进程替换 进程等待 进程等待的意义 如果子进程退出&#xff0c;父进程如果不管不顾&#xff0c;就可能造成‘僵尸进程’的问题&#xff0c;进而造成内存泄漏。 另外&#xff0c;进程一旦变成僵尸状态&#xff0c;那就刀枪不入&#xff…

5.5G的关键一跳!将数智未来照进现实

编辑&#xff1a;阿冒 设计&#xff1a;沐由 作为数字时代的三大思想家之一&#xff0c;乔治吉尔德在1993年就指出&#xff0c;未来25年内主干网的带宽每6个月增长一倍&#xff0c;其增长速度是摩尔定律预测的CPU增长速度的3倍。 这就是著名的吉尔德定律&#xff08;Gilder’s …

Qt开源项目:校医院远程诊断系统介绍

本人研一参考技术书籍开发的一款Qt程序&#xff0c;两年前已上传到GitHub&#xff0c;有兴趣的同学可以去看看。可能之前上传的项目不够完整&#xff0c;导致有一些同学没有在自己的环境上跑通&#xff0c;所以今天将整个工程都重新上传一遍&#xff0c;包括使用到的opencv的动…

Lambda 表达式中的变量必须是 final 的吗

如果我们定义了一个变量&#xff0c;想要在Lambda 表达式中修改变量的值&#xff0c;编译器会发出警告&#xff1a;“variable used in lambda expression should be final or effectively final”。 比如对一个list进行遍历&#xff0c;遍历的过程中对i进行操作 Java 规范中…

浅理解 ES6 新增的数组方法Array.of() 和 Array.from()

文章目录 &#x1f4cb;前言&#x1f3af;Array.of() 方法&#x1f3af;Array.from() 方法&#x1f3af;二者区别&#x1f4dd;最后 &#x1f4cb;前言 在前端开发的面试过程中&#xff0c; ES6 新增是一个很常见的考点&#xff0c;比如说箭头函数、模板字符串、let 和 const …

宁波汽车运输集团:引入二维码技术,实现车辆精细化管理

宁波市汽车运输集团有限公司是宁波市道路货运业的龙头企业之一&#xff0c;主营全国各地的普通货运以及货物专用运输&#xff08;集装箱、罐式&#xff09;。 作为汽车运输集团&#xff0c;车辆的安全问题极其重要。因此&#xff0c;公司设备安全部门要求每个驾驶员在作业之前…

netfilter filter表(二)

这次继续分析filter表&#xff0c;不同与之前的分析方式&#xff0c;这次通过将内核中的数据打印出来&#xff0c;对比结构关系图来分析。这是本次分析涉及的几个数据结构&#xff1a; struct xt_table { struct list_head list; /* What hooks you will enter on */ unsigned …

4、SpringBoot接收和响应xml报文请求

背景 平时开发的接口&#xff0c;基本是使用 json 格式的请求报文。然而&#xff0c;有时候也避免不了有 xml 报文请求的场景&#xff0c;最近就遇到了这种情况&#xff0c;在此记录下。另外&#xff0c;工程中使用的是 controller-service……这种结构。 xml请求报文&#x…

链表(JS实现)

&#x1f4dd;个人主页&#xff1a;爱吃炫迈 &#x1f48c;系列专栏&#xff1a;数据结构与算法 &#x1f9d1;‍&#x1f4bb;座右铭&#xff1a;道阻且长&#xff0c;行则将至&#x1f497; 文章目录 链表链表的分类创建链表LinkedList类的骨架 实现链表的方法push尾部添加元…

chatgpt智能提效职场办公-ppt怎么蒙层

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 在 PowerPoint 中添加蒙版图层&#xff0c;可以在幻灯片中创建一个半透明的矩形或形状&#xff0c;并在其上方添加或放置其他对象。 下…

FPGA终于可以愉快地写代码了!Vivado和Visual Studio Code黄金搭档

如果你是一位FPGA开发者&#xff0c;那么你一定会对VIvado这款软件非常熟悉。但是&#xff0c;对于vivado兼容的第三方编辑器软件&#xff0c;你知道Visual Studio Code吗&#xff1f;这是个非常不错的选择&#xff0c;Visual Studio Code搭配众多插件&#xff0c;能让你FPGA开…