二叉搜索树(BSTree)

news2024/10/5 1:20:56

目录

一、二叉搜索树

 二、二叉搜索树的接口及实现

1、二叉搜索树的查找

2、二叉搜索树的插入

3、二叉搜索树的删除

三、二叉搜索树的递归版本

本期博客主要分享二叉搜索树的底层实现。(主要是笔记,供自己复习使用😂)

一、二叉搜索树

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

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值。

若它的右子树不为空,则右子树上所有节点的值都大于根节点的值。

它的左右子树也分别为二叉搜索树。

 二、二叉搜索树的接口及实现

1、二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边查找,比根小则往左边走查找。

b、最多查找高度次,如果走到空,还没找到,这个值在树中不存在。

代码实现:

bool Find(const K& key)
		{
			Node* cur = _root;
			while (cur)
			{
				if (key < cur->_key)
				{
					//去左树
					cur = cur->_left;
				}
				else if (key > cur->_key)
				{
					//去右树
					cur = cur->_right;
				}
				else
				{
					//找到了
					return true;
				}
			}
			return false;
		}

2、二叉搜索树的插入

a、树为空,则直接新增节点,赋值给root指针

b、树不空,按二叉搜索树性质查找插入位置,插入新节点。

代码实现:

//插入
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root == new Node(key);
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			//查找插入位置
			while (cur)
			{
				if (cur->_key < key)
				{
					//去右树
					parent = cur;
					cur = cur->_right;

				}
				else if (cur->_key > key)
				{
					//去左树
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					//相等
					return false;
				}
			}
			//找到插入位置
			//判断我是左子还是右子
			//因为cur为空,所以要根据值来判断
			cur = new Node(key);
			if (key < parent->_key)
			{
				parent->_left=cur;
			}
			else
			{
				parent->_right=cur;
			}
			return true;
		}

3、二叉搜索树的删除

删除比较麻烦。我们要对它的几种情境进行分析。

a、要删除的节点无孩子节点

b、要删除的节点只有左孩子节点

c、要删除的节点只有右孩子节点

d、要删除的节点有左、右孩子节点

实际情况a可以和情况b或者情况c一块处理。如果右孩子为空,则托孤给父亲节点它的左孩子。如果左孩子为空,则托孤给父亲节点它的右孩子。如果左右孩子都不为空,则要找替换节点。

替换规则:

找右子树的最左节点(右子树值最小),或者找左子树的最右节点(左子树值最大)与要删除节点替换。目的是为了满足根大于左子树而小于右子树。

代码:

bool Erase(const K& key)
		{
			//左孩子为空
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur)
			{
				if (cur->_key < key)
				{
					//key大于_key--去右子树查找
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
					//左子树
				}
				else
				{
					//找到了
					//分为三种情况
					if (cur->_left == nullptr)
					{
						//左孩子为空
						//托孤右孩子
						//判断cur是parent左孩子还是右孩子
						if (cur == _root)//考虑删根的情况
						{
							_root = cur->_right;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
							}
							else
							{
								//cur是右孩子
								parent->_right = cur->_right;
							}
						}
						delete cur;
					}
					else if (cur->_right == nullptr)
					{
						//右为空--托孤左孩子
						if (cur == root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
							}
							else
							{
								//cur是右孩子
								parent->_right = cur->_left;
							}
						}
						delete cur;
					}
					else
					{
						//左右都不为空
						Node* minRight = cur->_right;//找右子树的最左节点
						parent = cur;
						while (minRight->_left)
						{
							parent = minRight;
							minRight = minRight->_left;
						}
						cur->_key = minRight->_key;
						//最左节点,不可能有左孩子,只可能有右孩子
						if (minRight == parent->_left)
						{
							parent->_left = minRight->_right;
						}
						else
						{
							parent->_right = minRight->_right;
						}
						delete minRight;
					}
					return true;
				}
			}
			return false;
		}

以上是循环版本主要接口的实现。而二叉搜索树递归版本也是非常有趣的。

三、二叉搜索树的递归版本

template<class K>
	struct BSTreeNode
	{
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;
		BSTreeNode(const K& key)
			:_left(nullptr)
			,_right(nullptr)
			,_key(key)
		{}
	};

	template<class K>
	class BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		BSTree()
			:_root(nullptr)
		{}

		BSTree(const BSTree<K>& copyt)
		{
			//拷贝构造
			_root = Copy(copyt._root);

		}

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

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		
		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}
		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}
		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
	private:
		bool _InsertR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}
			if (root->_key < key)
			{
				return _InsertR(root->_right, key);
			}
			else if (root->_key > key)
			{

				return _InsertR(root->_left, key);
			}
			else
				return false;
			
		}
		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;
			if (key > root->_key)
			{
				//右子树
				return _FindR(root->_right, key);
			}
			else if (key < root->_key)
			{
				return _FindR(root->_left, key);
			}
			else
				return true;
		}
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				return false;
			}
			if (root->_key < key)
			{
				return _EraseR(root->_right,key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				//找到要删除的节点
				//替换删除?
				if (root->_left == nullptr)
					root = root->_right;
				else if (root->_right == nullptr)
					root = root->_left;
				else
				{
					//左右都不为空
					//找左子树的最右节点,或者右子树的最左节点
					Node* minRight = root->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}
					//左为空
					swap(root->_key, minRight->_key);
					//交换值后转换成在子树中去删除节点
					return _EraseR(root->_right, key);
				}
				delete del;
				return true;
			}
		}
		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 Destory(Node* root)
		{
			if (root == nullptr)
				return;
			Destory(root->_left);
			Destory(root->_right);
			delete root;
		}
		void _InOrder(Node* root)
		{
			//根左右
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}
		Node* _root;
	};

递归版本实现非常巧妙的地方在于插入接口和删除接口的实现:

他们使用的是root地址的引用而不是地址的拷贝,这一点很是灵巧,博主就不多说大家细细品味其中的妙处使得代码大大简化。

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

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

相关文章

Github创建组织(organization)

目录 前言 Github上创建组织的详细步骤 前言 创建 Github 组织&#xff08;Organization&#xff09;可以让你和你的团队共享代码&#xff0c;更好地管理和协作开发项目。Github 组织&#xff08;Organization&#xff09;是一个非常有用的工具&#xff0c;可以让开发者协同…

stm32cubemx IAP升级(一)

stm32cubemx IAP升级- Bootloader的制作 板卡&#xff1a;Nucleo-L412 平台&#xff1a;macbook pro 工具&#xff1a;vscode stm32cubemx stm32cubeProgramer cmake toolchain 分区 L412 自带128K的flash&#xff0c;所以我们可以这样分区&#xff0c; printf(“| flash pr…

crypto-js AES-CTR 实现密文前缀式局部解密细节 踩坑点

项目有需求&#xff0c;长明文经过AES-CTR模式加密后&#xff0c;在解密的时候&#xff0c;密文不能直接得到&#xff0c;每次通过某些方法尝试后&#xff0c;只能得到一块密文&#xff08;按顺序&#xff09;&#xff0c;所以只能一块一块的拼接解密。在使用crypto-js这个库的…

WooCommerce可扩展性:如何扩大您的WooCommerce商店

有了合适的人和技术&#xff0c;WooCommerce可扩展性绝对是很大的&#xff01; 事实上&#xff0c;使用WooCommerce作为您的电子商务平台&#xff0c;您的在线商店的规模可以与您的目标和愿望一样大&#xff01; 根据自定义模板开发高性能品牌电子商务网站 全球超500万个电商…

高效办公——Excel表格-02篇(if函数常见用法 + 条件格式的使用)

高效办公——Excel表格-02篇&#xff08;if函数常见用法 条件格式的使用&#xff09;1. if单条件简单用法1.1 简单需求1.2 实现方法2. if多条件使用(if-else的情况)3. if多条件使用(if(A && B)的情况)3.1 简单需求3.2 实现需求4. if多条件使用(if(A || B)的情况)5. 条…

亚马逊云科技“三步走”,实现区块链应用的快速开发

作为数字技术的代表之一&#xff0c;区块链技术正在被越来越多的企业所重视&#xff0c;并被引入到各行业的数字化转型中。根据中国通信院数据显示&#xff0c;目前中国区块链应用场景主要以金融和互联网为主&#xff0c;但应用范围呈现不断拓展的态势&#xff0c;政务数据共享…

day10_oop

今日内容 零、 复习昨日 一、面向对象的概念 二、面向对象编程 三、内存图 零、 复习昨日 晨考复习… 一、作业 package com.qf.homework;import java.util.Arrays;/*** --- 天道酬勤 ---** author QiuShiju* desc* ----------------* 引用数据类型的默认初始值null*/ public …

Nginx 正向代理、方向代理、端口转发

正向代理就是客户端代理&#xff0c;代理客户端&#xff0c;服务端不知道实际发起请求的客户端 正向代理中&#xff0c;proxy和client一般同一个lan或者网络可达&#xff0c;server与client一般不可达&#xff08;缓存场景除外&#xff09; 正向代理类似一个跳板机&#xff0c…

下一个“AI王炸”,别只盯着OpenAI,DeepMind也在憋大招

过去几个月&#xff0c;OpenAI风头无两&#xff0c;各大科技公司争先恐后地跟进大语言模型&#xff08;LLM&#xff09;这一技术路线。 对比之下&#xff0c;OpenAI的老对手DeepMind&#xff0c;显得有些低调和沉默。微软靠OpenAI打了一场胜仗&#xff0c;而谷歌推出的Bard翻了…

【c++初阶】命名空间的定义

命名空间的定义一.缺陷二.namespace和::三.访问namespace四.一些注意1.工程里标准库的展开2.命名域的小技巧一.缺陷 在c语言中&#xff0c;如果我们同时定义一个全局变量和一个局部变量并且使用同一个名称的话&#xff0c;是可以编过的&#xff08;因为全局和局部是属于两个不同…

云原生_kubernetes(k8s)_pod介绍以及配置信息说明

目录 一、Pod介绍 1、Pod结构 2、Pod定义 二、Pod配置 1、基本配置 2、镜像拉取 3、启动命令 4、环境变量 5、端口设置 6、资源配额 一、Pod介绍 1、Pod结构 每个Pod中都可以包含一个或者多个容器&#xff0c;这些容器可以分为两类&#xff1a; 用户程序所在的容器&…

网络编程(第二章: TCPUDP基础模型)

TCP/UDP&#xff08;服务器、客户端源码&#xff09; [(12条消息) 网络编程(4.7作业)(TCP/UDP源代码)_m0_37565374的博客-CSDN博客]: 一. 套接字 socket 1.概念 最早的socket和消息队列、共享内存&#xff0c;管道一致只能实现一台主机中的多个进程间通信。后期加入了TCP/I…

云日记个人中心项目思路

验证昵称的唯一性 前台&#xff1a; 昵称文本框的失焦事件 blur 1. 获取昵称文本框的值 2. 判断值是否为空 如果为空&#xff0c;提示用户&#xff0c;禁用按钮&#xff0c;并return 3. 判断昵称是否做了修改…

一文详解:linux部署jenkins,一键构建并部署springboot至第三方服务器

目录 1、下载jenkins 2、 启动jenkins 3、访问jenkins 4、在当前Linux上安装maven 4.1、更新wget命令&#xff0c;支持https请求 4.2、下载maven 4.3、解压安装maven 4.4、配置maven环境变量 4.5、maven配置阿里云镜像 4.6、配置maven依赖下载的位置 5、Linux安装Gi…

Redis的使用【Redis】

一、缓存简介 缓存简介 二、缓存分类 缓存分类 三、常见缓存 常见缓存 四、Redis使用 Redis 有 5 ⼤基础数据类型&#xff1a; String——字符串类型Hash——字典类型List——列表类型Set——集合类型ZSet——有序集合类型 其中最常⽤的是字符串和字典类型。 1.字符…

Vulnhub靶场DC-1练习

目录0x00 准备0x01 主机信息收集0x02 站点信息收集0x03 漏洞查找与利用0x00 准备 下载链接&#xff1a;https://download.vulnhub.com/dc/DC-1.zip 介绍&#xff1a;There are five flags in total, but the ultimate goal is to find and read the flag in root’s home dir…

常见的DNS攻击与防御

DNS查询通常都是基于UDP的&#xff0c;这就导致了在查询过程中验证机制的缺失&#xff0c;黑客很容易利用该漏洞进行分析。DNS服务可能面临如下DNS攻击风险&#xff1a; 黑客伪造客户端源IP地址发送大量的DNS请求报文&#xff0c;造成DNS request flood攻击。黑客伪造成授权服…

Node.js安装与配置步骤

前言一、安装Node.js1.下载2.安装3.添加环境变量二、验证是否安装成功三、修改模块下载位置1.查看npm默认存放位置2.在 nodejs 安装目录下&#xff0c;创建 “node_global” 和 “node_cache” 两个文件夹3.修改默认文件夹4.测试默认位置是否更改成功四、设置淘宝镜像1.将npm默…

ARM Linux 内核启动2 ——C语言阶段

一、内核启动的C语言阶段 1 1、这一块的学习思路 (1) 抓大放小&#xff0c;不深究。 (2) 感兴趣可以就某个话题去网上搜索资料学习。 (3) 重点局部深入分析。 2、具体学习方法 (1) 顺着代码执行路径抓全。这是我们的学习主线。 (2) 对照内核启动的打印信息进行分析。 3、…

Ansible批量部署采集器

千台服务器部署采集器的时候用到了 Ansible&#xff0c;简单记录一下。 安装 Ansible pip install ansible yum install ansible –y在 /etc/ansible/hosts 中添加被管理组 &#xff0c;比如图中[web] 是组的名字。 执行ansible命令测试&#xff0c;通过hosts中定义的web组执…