数据结构之搜索二叉树

news2024/11/15 8:37:32

目录

一、什么是搜索二叉树

基本概念

特点

注意事项

二、搜索二叉树的C++实现

2.0 构造与析构

2.1 插入

2.2 查找

2.3 删除

2.3.1 无牵无挂型

2.3.2 独生子女型

2.3.3 儿女双全型

三、搜索二叉树的应用

3.1 key搜索

3.2 key/value搜索


一、什么是搜索二叉树

搜索二叉树,通常称为二叉搜索树(BST,Binary Search Tree),是一种特殊的二叉树数据结构。

基本概念

  • 节点结构:二叉搜索树由一系列节点组成,每个节点包含一个键值(用于比较)和一个与之关联的值。
  • 树结构:每个节点最多有两个子节点,分别称为左子节点和右子节点。

特点

  • 有序性:二叉搜索树的核心特点是有序性。对于树中的任意节点:
    • 所有左子树的节点键值均小于该节点的键值。
    • 所有右子树的节点键值均大于该节点的键值。
  • 查找效率:由于有序性,二叉搜索树可以在对数时间内(树的高度次)完成查找、插入和删除操作,即时间复杂度为O(log n),其中n是树中节点的数量。
  • 动态性:二叉搜索树支持动态数据集合的操作,允许在运行时添加或删除元素。

注意事项

  • 当二叉搜索树不平衡时,其性能可能会退化到接近线性时间复杂度O(n),此时可能需要使用平衡二叉搜索树(如AVL树或红黑树)来保证操作的效率。具体请关注博主后续博客

二、搜索二叉树的C++实现

2.0 构造与析构

template<class K>
struct BSTNode
{
	K _key;
	BSTNode<K>* _left;
	BSTNode<K>* _right;

	BSTNode(const K& key)
		:_key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}
};
~BSTree()
{
	Destroy(_root);
	_root = nullptr;
}
//递归删除
void Destroy(Node* root)
{
	if (root == nullptr)
		return;

	Destroy(root->_left);
	Destroy(root->_right);
	delete root;
}
BSTree(const BSTree& t)
{
	_root = Copy(t._root);
}

BSTree& operator=(BSTree tmp)
{
	swap(_root, tmp._root);
	return *this;
}
//递归复制
Node* Copy(Node* root)
{
	if (root == nullptr)
		return nullptr;

	Node* newRoot = new Node(root->_key, root->_value);
	newRoot->_left = Copy(root->_left);
	newRoot->_right = Copy(root->_right);
	return newRoot;
}

2.1 插入

思路

  • 树为空,则直接新增结点,赋值给root指针
  • 树不空,按⼆叉搜索树性质,插入值比当前结点⼤往右⾛,插入值比当前结点小往左⾛,找到空位置,插⼊新结点。
  • 如果⽀持插⼊相等的值,插⼊值跟当前结点相等的值可以往右⾛,也可以往左⾛,找到空位置,插入新结点。

难点

  • 为提升效率,不采用递归做法,采用循环模拟递归
  • 二叉搜索树的性质决定了必有一个空节点会存放当前key值
  • 为了维持二叉搜索树的性质,需要额外引入parent指针。最后插入的位置需要与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->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	node* newnode = new node(key);
	if (parent->_key > key)
	{
		parent->_left = newnode;
	}
	else
	{
		parent->_right = newnode;
	}
	return true;
}

2.2 查找

思路

  1. 从根开始⽐较,查找x,⽐根的值⼤则往右边⾛,⽐根值⼩则往左边⾛。
  2. 最多查找⾼度次,⾛到到空,还没找到,这个值不存在。
  3. 本文实现不支持插入冗余值(模拟实现STL库中的set)
  4. 如果要模拟实现STL库中的multiset,STL库中实现的是返回中序遍历的第一个相等的节点
bool Find(const K& key)
{
	node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
		else
		{
			return true;
		}
	}
	return false;
}

2.3 删除

删除有如下三种情况

2.3.1 无牵无挂型

把结点的⽗亲对应孩⼦指针指向空,直接删除N结点

2.3.2 独生子女型

把N结点的⽗亲对应孩⼦指针指向N的右/左孩⼦,直接删除N结点

2.3.3 儿女双全型

采用替代法:

  1. 左子树的最⼤结点(最右结点)或者右子树的最⼩结点(最左结点)替代N,直接复制值替代即可
  2. 然后转成删除替代节点

bool Erase(const K& key)
{
	node* parent = nullptr;
	node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;
		}
		//删除
		else
		{
			//如果左为空
			if (cur->_left == nullptr)
			{
				//考虑极端情况
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
				delete cur;
			}
			// 如果右为空
			else if (cur->_right == nullptr)
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
				delete cur;
			}
			else
			{
				// 左右都不为空
				// 右子树最左节点
				Node* replaceParent = cur;
				Node* replace = cur->_right;
				while (replace->_left)
				{
					replaceParent = replace;
					replace = replace->_left;
				}

				cur->_key = replace->_key;

				//最左节点依然可能有最右节点
				if (replaceParent->_left == replace)
					replaceParent->_left = replace->_right;
				else
					replaceParent->_right = replace->_right;

				delete replace;
			}

			return true;
		}
	}
	return false;
}

三、搜索二叉树的应用

3.1 key搜索

Key搜索指的是使用“key”(关键字或关键项)作为检索依据来快速查找和检索数据的过程。在二叉搜索树中,通过比较节点的Key与要查找的Key,可以确定搜索方向(向左或向右),直到找到匹配的节点或确定目标不存在。

3.2 key/value搜索

Key/Value搜索是一种基于键值对(Key-Value Pair)的数据检索方式。在这种模式下,数据被组织成一系列的键值对,每个键值对由一个唯一的键(Key)和一个与之相关联的值(Value)组成。搜索时,用户通过提供键来查询对应的值。

Key/Value搜索的实现方式常用的有以下几种

  1. 哈希表
    • 哈希表是实现Key/Value搜索的一种常见数据结构。通过哈希函数将键映射到表的某个位置,以快速定位到对应的值。
  2. B树及其变种
    • 在一些需要持久化存储的Key/Value数据库中,可能会使用B树(如B+树)或其变种来存储数据。这些数据结构通过保持数据的有序性来优化搜索和范围查询的性能。
  3. 分布式哈希表
    • 在分布式系统中,分布式哈希表(DHT)是一种用于实现Key/Value搜索的分布式数据结构。它通过将键分散到多个节点上来实现数据的分布式存储和检索。

本文介绍用二叉搜索树实现key/value搜索

#pragma once
#include<iostream>
using namespace std;

namespace key_value
{
	template<class K, class V>
	struct BSTNode
	{
		K _key;
		V _value;

		BSTNode<K, V>* _left;
		BSTNode<K, V>* _right;

		BSTNode(const K& key, const V& value)
			:_key(key)
			, _value(value)
			, _left(nullptr)
			, _right(nullptr)
		{}
	};

	template<class K, class V>
	class BSTree
	{
		typedef BSTNode<K> Node;
	public:
		// 强制生成构造
		BSTree() = default;

		BSTree(const BSTree& t)
		{
			_root = Copy(t._root);
		}

		BSTree& operator=(BSTree tmp)
		{
			swap(_root, tmp._root);
			return *this;
		}

		~BSTree()
		{
			Destroy(_root);
			_root = nullptr;
		}

		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->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_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->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}

			return nullptr;
		}

		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else
				{
					if (cur->_left == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_right;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_right;
							}
							else
							{
								parent->_right = cur->_right;
							}
						}
						delete cur;

					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (parent->_left == cur)
							{
								parent->_left = cur->_left;
							}
							else
							{
								parent->_right = cur->_left;
							}
						}

						delete cur;

					}
					else
					{
						Node* replaceParent = cur;
						Node* replace = cur->_right;
						while (replace->_left)
						{
							replaceParent = replace;
							replace = replace->_left;
						}

						cur->_key = replace->_key;

						if (replaceParent->_left == replace)
							replaceParent->_left = replace->_right;
						else
							replaceParent->_right = replace->_right;

						delete replace;
					}

					return true;
				}
			}

			return false;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}
		//递归删除
		void Destroy(Node* root)
		{
			if (root == nullptr)
				return;

			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
		}
		//递归复制
		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);
			return newRoot;
		}
	private:
		Node* _root = nullptr;
	};
}

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

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

相关文章

EAGLE——探索混合编码器的多模态大型语言模型的设计空间

概述 准确解释复杂视觉信息的能力是多模态大型语言模型 (MLLM) 的关键重点。最近的研究表明&#xff0c;增强的视觉感知可显著减少幻觉并提高分辨率敏感任务&#xff08;例如光学字符识别和文档分析&#xff09;的性能。最近的几种 MLLM 通过利用视觉编码器的混合来实现这一点…

科研绘图系列:R语言ggplot2画热图(heatmap)

文章目录 介绍加载R包导入数据数据预处理画图导出数据系统信息介绍 热图(Heatmap)是一种数据可视化技术,它通过颜色的变化来表示数据的大小或者密度。热图通常用于展示两个变量之间的关系,或者在二维空间上展示数据的分布情况。以下是热图可以表示的一些内容: 数据分布:…

网络原理 HTTP与HTTPS协议

博主主页: 码农派大星. 数据结构专栏:Java数据结构 数据库专栏:MySQL数据库 JavaEE专栏:JavaEE 关注博主带你了解更多计算机网络知识 目录 1.HTTP概念 2.HTTP报文格式 3.HTTP请求 1.首行 1.1URL 1.2 GET⽅法 1.3 POST⽅法 1.4 其他⽅法 2.请求头&#xff08;head…

JVM面试问题集

什么是JVM? 了解过字节码文件的组成吗? 说一下运行时数据区 哪些区域会出现内存溢出&#xff0c;会有什么现象? JM在JDK6-8之间在内存区域上有什么不同 类的生命周期 什么是类加载器 什么是双亲委派机制 打破双亲委派机制 Tomcat的自定义类加载器

【网络通信基础与实践番外一】多图预警之图解UDP和TCP前置知识

参考大佬的文章https://www.cnblogs.com/cxuanBlog/p/14059379.html 一、宏观架构中的传输层 在计算机中&#xff0c;任何一个可以交换信息的介质都可以称为端系统。计算机网络的运输层则负责把报文从一端运输到另一端&#xff0c;运输层实现了让两个互不相关的主机进行了逻辑…

Kafka-Manager安装及操作

文章目录 一、kafka-manager介绍二、kafka-manager安装三、Kafka-Manager操作 一、kafka-manager介绍 CMAK (Cluster Manager for Apache Kafka, previously known as Kafka Manager) CMAK (previously known as Kafka Manager) is a tool for managing Apache Kafka cluster…

STM32篇:开发环境安装

编程语言&#xff1a;C语言 需要安装的软件有两个&#xff1a;Keil5 和 STM32CubeMX 一.Keil5 的安装 使用 Keil4 写 STM32 代码其实也是可以&#xff0c;但需要很复杂的配置&#xff0c;不建议新手操作。 比较推荐 Keil5 编写 STM32 &#xff0c;只需要一些简单的设置就可…

(一)Lambda-Stream流

概述 Java8的Stream使用的是函数式编程模式&#xff0c;它可以被用来对集合或数组进行链状流式的操作&#xff0c;可以更方便地让我们对集合或数组操作。 使用Stream流程&#xff1a; 创建流 -> 中间操作 -> 终结操作; 注&#xff1a;必须要有终结操作否则中间操作不生效…

hive-拉链表

目录 拉链表概述缓慢变化维拉链表定义 拉链表的实现常规拉链表历史数据每日新增数据历史数据与新增数据的合并 分区拉链表 拉链表概述 缓慢变化维 通常我们用一张维度表来维护维度信息&#xff0c;比如用户手机号码信息。然而随着时间的变化&#xff0c;某些用户信息会发生改…

7.搭建个人金融数据库之快速获取股票列表和基本信息!

前边我们提过&#xff0c;免费的数据一般来自于爬虫&#xff0c;获取难度和维护成本都比较高&#xff0c;其实不太适合小白用户。所以非必要情况下&#xff0c;我们尽量不用这种方式来获取数据。 我自己用的比较多的是tushare&#xff0c;一般来说有它也就够了&#xff0c;大…

Junit4测试报错:java.lang.NoClassDefFoundError: org/junit/runner/manipulation/Filter

原来build path 界面&#xff1a; Junit为Modulepath 应把Junit改为Classpath即可&#xff0c;如下图所示&#xff1a;

前端和后端的相对路径和绝对路径

1. 相对路径访问图片 test.html 位于 web/a/b/c/ 目录中&#xff1a; 若要访问 static/img/ 文件夹中的图片&#xff08;假设图片名为 image.png&#xff09;&#xff0c;相对路径应该是&#xff1a; <img src"../../../static/img/image.png" alt"Image&quo…

Java笔试面试题AI答之设计模式(3)

文章目录 11. Spring开发中的哪里使用了工厂设计模式 &#xff1f;1. BeanFactory2. 工厂方法模式3. 抽象工厂模式4. 示例说明总结 12. 什么是代理模式 &#xff1f;13. 请列举代理模式的应用场景 &#xff1f;14. 什么是原型模式 &#xff1f;15. 请简述Java中原型模式的使用方…

Mixamo动画使用技巧

1、登录Mixiamo网站 2、下载人物模型 3、找到FBX文件 选中人形骨骼 3、下载动画 4、拖拽FBX 5、注意事项 生成的FBX文件中会包含一个骨骼一个动画 如果人物有骨骼&#xff0c;则不需要&#xff0c;没有需要对应此包中的骨骼&#xff0c;骨骼不可以通用&#xff0c;动画通用 …

百度智能云API调用

植物识别API import base64 import urllib import requestsAPI_KEY "你的图像识别API_KEY" SECRET_KEY "你的图像识别SECRET_KEY"def main():url "https://aip.baidubce.com/rest/2.0/image-classify/v1/plant?access_token" get_access_t…

[spring]应用分层 及 Spring IoCDI

文章目录 一. 应用分层二. Spring IoC获取String中的对象五大 类注解1. Controller (控制器存储)2. Service&#xff08;服务存储&#xff09;3. Repository(仓库存储)4. Conponent(组件存储)5. Configuration(配置存储) 方法注解Bean定义多个对象重命名 三. Spring DI属性注入…

排序-----归并排序(递归版)

核心思想&#xff1a;假设数组前后两部分各自有序&#xff0c;然后各定义两个指针&#xff0c;谁小谁放到新开辟的数组里面&#xff0c;最后把新开辟的数组赋值给原数组就完成了。要使前后两部分有序就采用递归的方式&#xff0c;不断往下划分块&#xff0c;最后一层划分为两个…

springboot实战学习(7)(JWT令牌的组成、JWT令牌的使用与验证)

接着上篇博客的学习。上篇博客是在基本完成用户模块的注册接口的开发以及注册时的参数合法性校验的基础上&#xff0c;基本完成用户模块的登录接口的主逻辑以及提到了问题&#xff1a;"用户未登录&#xff0c;需要通过登录&#xff0c;获取到令牌进行登录认证&#xff0c;…

Unity对象池的高级写法 (Plus优化版)

唐老师关于对物体分类的OOD的写法确实十分好&#xff0c;代码也耦合度也低&#xff0c;但是我有个简单的写法同样能实现一样的效果&#xff0c;所以我就充分发挥了一下主观能动性 相较于基本功能&#xff0c;这一版做出了如下改动 1.限制了对象池最大数量&#xff0c;多出来的…

Pybullet 安装过程

Pybullet 安装过程&#xff08;windows&#xff09; 1. 安装C编译工具2. 安装Pybullet 1. 安装C编译工具 pybullet 需要C编译套件&#xff0c;直接装之前检查下&#xff0c;要不会报缺少某版本MVSC的error&#xff0c;最好的方式是直接下载visual studio&#xff0c;直接按默认…