【数据结构】模拟实现二叉搜索树

news2024/11/15 8:34:42

文章目录

  • 1. 二叉搜索树的实现
  • 2. 二叉搜索树的应用
  • 3. 改造二叉搜索树为 KV 结构
  • 4. 二叉搜索树的性能分析

在这里插入图片描述

1. 二叉搜索树的实现

namespace key
{
	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)
		{}
	};

	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)
		{
			swap(_root, t._root);
			return *this;
		}

		~BSTree()
		{
			Destroy(_root);
		}

		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 false;
				}
			}

			cur = new Node(key);
			if (parent->_key < key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_left = cur;
			}

			return true;
		}

		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;
		}

		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 (cur == parent->_right)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_right)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = 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;
		}

		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:
		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);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);
			return newRoot;
		}

		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << " ";
			_InOrder(root->_right);
		}

		bool _FindR(Node* root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key < key)
			{
				return _FindR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _FindR(root->_left, key);
			}
			else
			{
				return true;
			}
		}

		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 _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
			{
				Node* del = root;
				if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else
				{
					Node* rightMin = root->_right;
					while (rightMin->_left)
					{
						rightMin = rightMin->_left;
					}
					swap(root->_key, rightMin->_key);

					return _EraseR(root->_right, key);
				}
				delete del;
				return true;
			}
		}

	private:
		Node* _root = nullptr;
	};
}

2. 二叉搜索树的应用

  1. K 模型:K 模型即只有 key 作为关键码,结构中只需要存储 key 即可,关键码即为需要搜索到的值

    比如:给一个单词 word,判断该单词是否拼写正确,具体方式如下:

    • 以词库中所有单词集合中的每个单词作为 key,构建一颗二叉搜索树;
    • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  2. KV 模型:每一个关键码 key,都有与之对应的值 value,即 <Key, Value> 的键值对。这种方式在现实生活中非常常见:

    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文 <word, chinese> 就构成一种键值对;
    • 再比如统计单词个数,统计成功后,给定单词就可以快速找到其出现的次数,单词与其出现次数 <word, count> 就构成一种键值对

3. 改造二叉搜索树为 KV 结构

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		typedef BSTreeNode<K, V> Node;

		Node* _left;
		Node* _right;
		K _key;
		V _value;

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

	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		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 (cur == parent->_right)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->_left;
						}
						else
						{
							if (cur == parent->_right)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = 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;
		}

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}

	private:
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;

			_InOrder(root->_left);
			cout << root->_key << " ";
			cout << root->_value << endl;
			_InOrder(root->_right);
		}

	private:
		Node* _root = nullptr;
	};
}

测试代码:

namespace key_value
{
	void TestBSTree1()
	{
		// 输入单词,查找单词对应的中文翻译
		BSTree<string, string> dict;
		dict.Insert("string", "字符串");
		dict.Insert("tree", "树");
		dict.Insert("left", "左边、剩余");
		dict.Insert("right", "右边");
		dict.Insert("sort", "排序");
		// 插入词库中所有单词
		string str;
		while (cin >> str)
		{
			BSTreeNode<string, string>* ret = dict.Find(str);
			if (ret == nullptr)
			{
				cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
			}
			else
			{
				cout << str << "中文翻译:" << ret->_value << endl;
			}
		}
	}

	void TestBSTree2()
	{
		// 统计水果出现的次数
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
		"苹果", "香蕉", "苹果", "香蕉" };
		BSTree<string, int> countTree;
		for (const auto& str : arr)
		{
			// 先查找水果在不在搜索树中
			// 1、不在,说明水果第一次出现,则插入<水果, 1>
			// 2、在,则查找到的节点中水果对应的次数++
			//BSTreeNode<string, int>* ret = countTree.Find(str);
			auto ret = countTree.Find(str);
			if (ret == NULL)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ret->_value++;
			}
		}
		countTree.InOrder();
	}
}

4. 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。

对有 n 和节点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是节点在二叉搜索树的深度的函数,即节点越深,比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N

最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

问题:如果退化成单支树,二叉搜索树的性能就失去了。能否进行改进,不论按照什么次序插入关键码,都能使二叉搜索树的性能达到最优?

答:可以使用 AVL树红黑树 的特性,使单支树在达到一定深度时进行“旋转”,变回接近完全二叉树的形状,这个我们后面再谈。


END

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

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

相关文章

基于单片机的智能电机保护器设计

摘 要 电动机大量使用在现在工业生产和我们的日常生活中。但在实际的使用过程中&#xff0c;电动由于各种各样的原因经常出现故障导致电机损坏。因此保证电动机的正常运行十分必要。随着电机保护技术的迅速发展&#xff0c;智能电机保护器应运而生。智能电机保护器在今后的生产…

【数据结构】串的模式匹配(KMP+朴素模式匹配)

2.串的模式匹配 什么是字符串的模式匹配&#xff1f; 在主串中找到与模式串相同的子串&#xff0c;并返回其所在位置。 模式串&#xff1a;要匹配的一串。注&#xff1a;子串是主串的一部分&#xff0c;一定在主串中存在&#xff0c;但模式串不一定在主串中找得到。 2.1 朴素模…

upload文件上传漏洞复现

什么是文件上传漏洞&#xff1a; 文件上传漏洞是指由于程序员在对用户文件上传部分的控制不足或者处理缺陷&#xff0c;而导致的用户可以越过其本身权限向服务器上上传可执行的动态脚本文件。这里上传的文件可以是木马&#xff0c;病毒&#xff0c;恶意脚本或者WebShell等。“…

uniapp-vue3 项目初始化集成配置【开箱即用】

地址 https://gitee.com/charrie/vue3-uniapp-init 部分截图展示 技术说明 采用vue3viteuniapp技术栈&#xff0c;setup语法糖编码方式使用完全免费的sard-uniapp组件库引入unocss量子化样式引擎&#xff0c;动态css不用自己写样式&#xff0c;引用class即可&#xff0c;降低…

计算机网络(6)-----传输层

目录 一.传输层 二.UDP协议 1.UDP的特点&#xff1a; 2.UDP的首部格式&#xff1a; 3.UDP校验的过程&#xff1a; 三.TCP协议 1.TCP协议的特点 2.TCP报文段首部格式 3.TCP的连接管理 &#xff08;1&#xff09;连接建立&#xff08;三次握手&#xff09; &#xff0…

Spring Web MVC入门(1)

什么是Spring Web MVC? 定义:Spring Web MVC是基于Servlet构建的原始Web框架,从一开始就包含在Spring框架中.它的正式名称"Spring Web MVC"来自其源模块的名称(Spring-webmvc),但是它通常被称为"Spring MVC". 什么是Servlet? Servlet是一种实现动态页面…

【C++】排序算法

目录 一、排序算法概述 二、初级排序算法 三、进阶排序算法 四、分治思想排序 五、哈希思想排序 六、分割思想排序 一、排序算法概述 在C语言中&#xff0c;通常需要手写排序算法实现对数组或链表的排序&#xff0c;但是在C中&#xff0c;标准库中的<algorithm>头…

【兔子机器人】实现从初始状态到站立

一、遥想星空up主的方法 由于我有卡位结构&#xff0c;无法做到劈腿&#xff0c;而且底盘也不一样&#xff0c;无法使用此方法 但是其代码思想是可以借鉴的。 参考视频&#xff1a; 【【开源啦&#xff01;】无刷轮腿平衡机器人】 【精准空降到 01:16】 https://www.bilibili…

C++类和对象一

#include <iostream> using namespace std;//设计一个学生类 class CStudent {public: //公有成员void InputData(){cout << "请输入学号";cin >> sno;cout << "请输入姓名";cin >> sname;cout << "请输入分…

RabbitMq踩坑记录

1、连接报错&#xff1a;Broker not available; cannot force queue declarations during start: java.io.IOException 2.1、原因&#xff1a;端口不对 2.2、解决方案&#xff1a; 检查你的连接配置&#xff0c;很可能是你的yml里面的端口配置的是15672&#xff0c;更改为5672即…

第110讲:Mycat实践指南:指定Hash算法分片下的水平分表详解

文章目录 1.应用指定Hash算法分片的概念2.使用应用指定Hash算法分片对某张表进行水平拆分2.1.在所有的分片节点中创建表结构2.2.配置Mycat实现应用指定Hash算法分片的水平分表2.2.1.配置Schema配置文件2.2.2.配置Rule分片规则配置文件2.2.3.配置Server配置文件2.2.4.重启Mycat …

交换机/路由器的存储介质-思科

交换机/路由器的存储介质-思科 本文主要介绍网络设备的存储介质组成。 RAM(random-accessmemory&#xff0c;随机访问存储器) RAM中内容断电丢失&#xff0c;主要用于运行操作系统、运行配置文件、IP 路由表:、ARP 缓存、数据包缓存区。 ROM(read-only memory&#xff0c;只…

多做【二叉搜索树】【数组转化为树】Leetcode 108. 将有序数组转换为二叉搜索树

【二叉搜索树】【数组转化为树】Leetcode 108. 将有序数组转换为二叉搜索树 解法1 递归 ---------------&#x1f388;&#x1f388; 108. 将有序数组转换为二叉搜索树 题目链接&#x1f388;&#x1f388;------------------- 解法1 递归 给你一个整数数组 nums &#xff0…

【Vue2】组件通信

父子通信 父 -> 子 子 -> 父 props 校验 props: {校验的属性名: {type: 类型, // Number String Boolean ...required: true, // 是否必填default: 默认值, // 默认值validator (value) {// 自定义校验逻辑return 是否通过校验}} },data 的数据是自己的 → 随便改pr…

【leetcode+深度/广度优先搜索】841. 钥匙和房间 (DFS,BFS)

leetcode-cn&#xff1a;leetcode面试75道精华&#xff1a;https://leetcode.cn/studyplan/leetcode-75/ 841.钥匙和房间&#xff1a;https://leetcode.cn/problems/keys-and-rooms/description/ 一、题目&#xff1a;841. 钥匙和房间 有 n 个房间&#xff0c;房间按从 0 到 n…

面试经典-16- 环形链表

题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&#…

【数学建模】熵权法

之前我们学了层次分析法和topsis法&#xff0c;但是主观性十分强&#xff0c;有没有科学的方法得出权重呢&#xff1f;今天&#xff0c;我们来学习熵权法&#xff01; 基本概念&#xff1a; 熵权法&#xff0c;物理学名词&#xff0c;按照信息论基本原理的解释&#xff0c;信息…

尼伽OLED透明屏闪耀第24届中国零售业博览会,引领零售行业革新

2024 CHINA SHOP 第二十四届中国零售业博览会 3.13-15 上海 3.13-15日&#xff0c;第24届中国零售业博览会盛大开幕&#xff0c;起立科技&#xff08;旗下品牌&#xff1a;起鸿、尼伽&#xff09;携其自主研发的30寸OLED透明屏和移动AI透明屏机器人惊艳亮相&#xff0c;成为展…

租房网站|基于springboot框架+ Mysql+Java+B/S架构的租房网站设计与实现(可运行源码+数据库+设计文档+部署说明)

推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 目录 前台功能效果图 用户功能模块 管理员功能登录前台功能效果图 系统功能设计 数据库E-R图设计 lunwen参…

Redis-复制功能

0 序言 复制功能是Redis提供的多机功能中最基础的一个&#xff0c;这个功能是通过主从复制&#xff08;master-slave replication&#xff09;模式实现的&#xff0c;它允许用户为存储着目标数据库的服务器创建出多个拥有相同数据库副本的服务器&#xff0c;其中存储目标数据库…