二叉搜索树的操作、模拟实现、应用以及性能的介绍

news2024/12/24 8:14:22

一、二叉搜索树

1. 性质

        (1)如果左子树不是空,那么左子树上的所有节点的值都小于根节点的值

        (2)如果右子树不是空,那么右子树上的所有节点的值都大于根节点的值

        (3)左右子树也分别为二叉搜索树

       如下图:
        ① 下面这个情况不是二叉搜索树

                因为 5 作为 10 的右子树,是小于 10 的

        

        ②  下面是可行的

            

二、二叉搜索树的操作

         1. 二叉搜索树的查找

                (1) 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。

                (2) 最多查找高度次,走到到空,还没找到,这个值不存在。

         2. 二叉搜索树的插入

               (1) 插入的具体过程如下:

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

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

        3. 二叉搜索树的删除

        (1) 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

        ① 要删除的结点无孩子结点

        ② 要删除的结点只有左孩子结点 

        ③ 要删除的结点只有右孩子结点

        ④ 要删除的结点有左、右孩子结点

       (2) 看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

        情况①:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除

        情况②:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除

        情况③:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题--替换法删除

三、二叉搜索树的实现

#pragma once

#include <iostream>
using namespace std;


// 二叉搜索数 —— 去重
template <class K>
class BinarySearchTreeNode
{
public:
	BinarySearchTreeNode<K>* _left;
	BinarySearchTreeNode<K>* _right;
	K _key;

	BinarySearchTreeNode(const K& key)
		: _left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}

};


template <class K>
class BinarySearchTree
{
	typedef BinarySearchTreeNode<K> Node;
public:

	// 去重 所以不能有相同的 key 值结点
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}

		// parent 记录 cur 前一个结点 方便链接
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur != nullptr)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 相等 key 值 返回 false 去重
				return false;
			}
		}
		

		Node* newnode = new Node(key);

		if (key > parent->_key)
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}

		return true;
	}

	bool Find(const K& key)
	{
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}

		return false;
	}

	bool Erase(const K& key)
	{
		Node* prev = nullptr;
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key > cur->_key)
			{
				prev = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				prev = cur;
				cur = cur->_left;
			}
			else // 找到了
			{
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}


					if (cur == prev->_right)
					{
						prev->_right = cur->_right;
					}
					else
					{
						prev->_left = cur->_right;
					}

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

					if (cur == prev->_right)
					{
						prev->_right = cur->_left;
					}
					else
					{
						prev->_left = cur->_left;
					}

					delete cur;
					cur = nullptr;
				}
				else // 两边都不为 nullptr
				{
					// 找左子树的 最大 结点替换要删除的位置
					// 或者
					// 找右子树的 最小 结点替换要删除的位置
					// 最后删除

					// 找右子树的 最小 结点替换
					Node* prevmin = cur;
					Node* min = cur->_right;

					// 找完右子树最小结点之后 说明此时结点的右已经为 nullptr 了
					while (min->_left)
					{
						prevmin = min;
						min = min->_left;
					}

					swap(&cur->_key, &min->_key);

					// min 是 右节点 的最小结点
					// 找 min 的时候 min 的最左结点已经为 nullptr 了
					if (min->_right != nullptr)
					{
						if (min == prevmin->_left)
						{
							prevmin->_left = min->_right;
						}
						else
						{
							prevmin->_right = min->_right;
						}
					}

					delete min;
				}
				return true;
			}
		}
		return false;
	}

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



/
	// 递归版本
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

	bool EraseR()
	{
		return _EraseR(_root, key);
	}


	//BinarySearchTree()
	//{}

	// C++ 用法 强制编译器生成默认的构造
	BinarySearchTree() = default;

	BinarySearchTree(const BinarySearchTree<K>& T)
	{
		_root = _Copy(T._root);
	}

	~BinarySearchTree()
	{
		_Destory(_root);
	}

	BinarySearchTree& operator=(const BinarySearchTree<K>& T)
	{
		swap(_root, T._root);

		return *this;
	}

private:

	void _Destory(Node*& root)
	{
		if (root == nullptr)
		{
			return;
		}

		_Destory(root->_left);
		_Destory(root->_right);

		delete root->_key;
		root = nullptr;
	}

	Node* _Copy(Node* root)
	{
		if (root == nullptr)
		{
			return nullptr;
		}

		Node* CopyNode = new Node(root->_key);
		root->_left = _Copy(root->_left);
		root->_left = _Copy(root->_right);

		return CopyNode;
	}

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

		if (key > root->_key)
		{
			return _EarseR(root->_right, key);
		}
		else if (key < root->_key)
		{
			return _EarseR(root->_left, key);
		}
		else // 此时 cur 就是要删的
		{
			Node* del = root;
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if(root->_right == nullptr)
			{
				root = root->_left;
			}
			else
			{
				// 两边都不为 nullptr
				Node* min = root->_right;
				while (min->_left)
				{
					min = min->_left;
				}

				swap(min->_key, root->_key);

				// 左边肯定是 nullptr 以为找的 右子树 最小值 也就是 右子树最左边的值
				return _EraseR(root->_right, key);
			}
			delete min;
			return true;
		}
	}


	// & 是上一个 root->_right 或 root->_left 的 别名
	bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			// 所以这里可以
			root = new Ndoe(key);
			return true;
		}

		if (key > root->_key)
		{
			return _InsertR(root->_right, key);
		}
		if (key < root->_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;
		}
	}


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

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};

四、二叉搜索树的应用

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

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

                ① 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树

                ② 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。       

        2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:

                ① 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;

                ② 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

代码如下:

#pragma once


#include <iostream>
#include <string.h>

using namespace std;


// 二叉搜索数 —— 去重
template <class K, class V>
class BinarySearchTreeNode
{
public:
	BinarySearchTreeNode<K, V>* _left;
	BinarySearchTreeNode<K, V>* _right;
	K _key;
	V _value;

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

};


template <class K, class V>
class BinarySearchTree
{
	typedef BinarySearchTreeNode<K, V> Node;
public:

	// 去重 所以不能有相同的 key 值结点
	bool Insert(const K& key, const V& value)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}

		// parent 记录 cur 前一个结点 方便链接
		Node* parent = nullptr;
		Node* cur = _root;

		while (cur != nullptr)
		{
			if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else
			{
				// 相等 key 值 返回 false 去重
				return false;
			}
		}


		Node* newnode = new Node(key, value);

		if (key > parent->_key)
		{
			parent->_right = newnode;
		}
		else
		{
			parent->_left = newnode;
		}

		return true;
	}

	Node* Find(const K& key)
	{
		Node* cur = _root;
		while (cur != nullptr)
		{
			if (key > cur->_key)
			{
				cur = cur->_right;
			}
			else if (key < cur->_key)
			{
				cur = cur->_left;
			}
			else
			{
				return cur;
			}
		}

		return nullptr;
	}

	

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

private:

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

		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root = nullptr;
};


void Test_BST()
{
	BinarySearchTree<string, string> dict;
	dict.Insert("Erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("double", "双倍");
	dict.Insert("Find", "查找");
	string str;
	while (cin >> str)
	{
		BinarySearchTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
		{
			cout << "中文是:" << ret->_value << endl;
		}
		else
		{
			cout << "词库没有此单词" << endl;
		}
	}
}

上面简单改了一下二叉搜索树,改成了 key、value 结构。 

五、二叉搜索树的性能分析

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

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

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

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树)

最差情况下,二叉搜索树退化为单支树(或者类似单支)

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

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

相关文章

Redis的网络模型

计算机硬件包括CPU&#xff0c;内存&#xff0c;网卡 为了避免用户应用和操作系统内核产生冲突乃至内核崩溃&#xff0c;用户应用和内核是隔离开的 1)进程的寻址空间会被划分成两部分&#xff0c;内核空间和用户空间&#xff0c;内核和用户应用都无法直接访问物理内存&#xff…

【Linux】在Ubuntu上部署web项目(Ubuntu版本为16.04.7,jdk1.8、mysql5.7、tomcat9.0.48)

介绍 这里是小编成长之路的历程&#xff0c;也是小编的学习之路。希望和各位大佬们一起成长&#xff01; 以下为小编最喜欢的两句话&#xff1a; 要有最朴素的生活和最遥远的梦想&#xff0c;即使明天天寒地冻&#xff0c;山高水远&#xff0c;路远马亡。 一个人为什么要努力&a…

河道水位监测:河道水位监测用什么设备

中国地形复杂&#xff0c;气候多样&#xff0c;导致水资源分布不均&#xff0c;洪涝和干旱等问题时有发生。同时&#xff0c;人类活动也对水资源造成了很大压力&#xff0c;工业和农业用水增加&#xff0c;河道水位下降&#xff0c;生态环境受到威胁。因此&#xff0c;对河道水…

【面向对象三大特性之继承】

目录 1.什么是继承2.父类成员访问2.1 子类访问父类的成员变量2.1.1 子类和父类不存在同名成员变量2.1.2 子类和父类存在同名成员变量 2.2子类中访问父类的成员方法2.2.1. 成员方法名字不同2.2.22. 成员方法名字相同 3.super关键字4.子类构造方法5.super和this的异同点6.代码块的…

可视化探索开源项目的 contributor 关系

引语&#xff1a;作为国内外最大的代码托管平台&#xff0c;根据最新的 GitHub 数据&#xff0c;它拥有超 372,000,000 个仓库&#xff0c;其中有 28,000,000 是公开仓。分布式图数据库 NebulaGraph 便是其中之一&#xff0c;同其他开源项目一样&#xff0c;NebulaGrpah 也有自…

用arcgis for javascript 开发一个三维地图(入门案例)

效果如图&#xff1a; 详细的步骤就不啰嗦介绍了&#xff0c;大家可以参考上一篇文章二维地图入门案例&#xff0c;这里只是改了一点引用和属性而已。 核心代码&#xff1a; SceneView 用于创建三维地图 require([“esri/Map”, “esri/views/SceneView”] 这里提一句有两种…

vue项目中的环境变量的应用

vue项目中的环境变量的应用 在Vue项目中使用环境变量可以方便地在开发、测试、生产等不同环境中进行配置&#xff0c;而无需修改代码。 项目根目录下创建一个.env文件或者.env.[mode]文件&#xff0c;其中mode表示开发、测试、生产等不同的环境&#xff0c;文件名的后缀部分指…

Python的分布式网络爬虫

分布式爬虫其实就是指利用多台计算机分布式地从互联网上采集数据的一种爬虫。它可以把大规模的任务分解成若干小规模的&#xff0c;由多台计算机并行进行处理&#xff0c;大大提高了效率和速度。 分布式爬虫有很多优势&#xff1a;解决单机爬虫效率低的问题&#xff0c;分布式…

【UR3系统升级到CB3.12附带URcap1.05】

【UR3系统升级到CB3.12附带URcap1.05】 1. 前言1.1 Polyscope 3.12更新须知1.2 更新步骤 2. 对 PSU 电压进行控制的步骤2.1 启动机器人电源2.2 启动机器人程序2.3 查看PSU 电压 3. Polyscope 3.12 软件下载3.1 CB 系列机器人3.2 下载软件包URUP 4. CB3 软件安装的指导4.1 连接示…

Kohl‘s百货的EDI需求详解

Kohls是一家美国的连锁百货公司&#xff0c;成立于1962年&#xff0c;总部位于美国威斯康星州的门多西。该公司经营各种商品&#xff0c;包括服装、鞋子、家居用品、电子产品、化妆品等&#xff0c;并拥有超过1,100家门店&#xff0c;分布在美国各地。本文将为大家介绍Kohls的E…

SDK接口远程调试【内网穿透】

文章目录 1.测试环境2.本地配置3. 内网穿透3.1 下载安装cpolar内网穿透3.2 创建隧道 4. 测试公网访问5. 配置固定二级子域名5.1 保留一个二级子域名5.2 配置二级子域名 6. 使用固定二级子域名进行访问 转发自cpolar内网穿透的文章&#xff1a;Java支付宝沙箱环境支付&#xff0…

stable diffusion使用入门

目录 1、stable diffusion简要说明 2、安装stable-diffusion-webui &#xff08;1&#xff09;下载地址 &#xff08;2&#xff09;执行启动命令 3、Lora模型介绍 4、模型下载 &#xff08;1&#xff09;Lora模型使用 &#xff08;2&#xff09;底座模型使用 1、stable…

初识开源接口测试工具——Postcat

Postcat 是一个强大的开源、跨平台&#xff08;Windows、Mac、Linux、Browsers...&#xff09;的 API 开发测试工具&#xff0c;支持 REST、Websocket 等协议&#xff08;即将支持 GraphQL、gRPC、TCP、UDP&#xff09;&#xff0c;帮助你加速完成 API 开发和测试工作。 它适合…

OLAP和OLTP

1&#xff1a;OLAP和OLTP对比 数据库系统可以在广义上分为联机事务处理&#xff08;Online Transaction Process&#xff0c;OLTP&#xff09;和联机分析处理&#xff08;Online Analyze Process&#xff0c;OLAP&#xff09;两种面向不同领域的数据库&#xff0c;OLAP数据库也…

vue-cli的使用

什么是单页面应用程序? ​ 单页面应用程序(Single Page Application)简称SPA。指的是一个web网站中只有唯一的一个html页面,所有的功能与交互都在这个唯一的页面内完成。 什么是vue-cli? ​ vue-cli是Vue.js开发的标准工具。它简化了基于webpack创建工程化的vue项目过程。…

刷题常用算法模板(持续更新)

目录 1、二分查找2、线段树3、树状数组4、差分数组5、前缀树6、并查集7、AC自动机8、Morris遍历9、二叉树非递归遍历10、KMP11、Manacher12、快速选择 bfprt13、滑动窗口14、加强堆15、有序表16、单调栈 1、二分查找 需求&#xff1a;在一个有序数组中&#xff0c;快速查询某一…

chatgpt赋能python:Python中画笔放下:掌握Python图形编程

Python 中画笔放下&#xff1a;掌握 Python 图形编程 Python 是一种高级编程语言&#xff0c;广泛应用于数据处理、人工智能、Web 应用程序等领域。除了这些应用外&#xff0c;Python 还可以用于图形编程&#xff0c;包括绘制 2D 和 3D 图形、创建游戏和交互式应用程序等。在 …

Vue的基本使用

文章目录 Vue简介Vue的使用1.Vue指令2.过滤器3.侦听器4.计算属性 Vue简介 ​ Vue是一套用于构建用户界面的前端框架。 vue的两个特性 (1)数据驱动视图 在使用vue的页面中,vue会监听数据的变化,从而自动重新渲染页面的结构。当页面数据发生变化的时候,会自动重新渲染(数据的…

GUI JFrame实战:六一节,爱她就给她画个哆啦A梦吧

文章目录 前言技术积累容器分类主要APIGraphics图像 绘画方法实战演示1、创建哆啦A梦渲染类2、创建测试方法3、查看渲染结果 写在最后 前言 相信很多使用JAVA高级语言的同学都知道GUI图形用户界面&#xff0c;开发人员可以使用java.awt、javax.swing两个API绘画想要的图形并通…

蓝牙规范系列--基础篇(第一篇)

一、前言 玩过物联网的小伙伴肯定知道ESP32&#xff08;一款WiFi/BT SoC&#xff09;&#xff0c;那肯定也知道蓝牙这个东西&#xff0c;蓝牙技术最近几年由于蓝牙耳机很火&#xff0c;那蓝牙技术到底是怎样的呢&#xff1f; 蓝牙无线技术是一种短距离的通信系统旨在替换便携式…