【C++高阶】高效搜索的秘密:深入解析搜索二叉树

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:C++多态
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀二叉搜索树

  • 📒1. 二叉搜索树
    • 🎩二叉搜索树概念
    • 🎈二叉搜索树操作
  • 📕2. 二叉搜索树模拟实现
    • 🧩二叉搜索树结构
    • 🧩二叉搜索树操作
      • 🌈插入
      • 🌞遍历
      • 🌙查找
      • ⭐删除
    • 🧩二叉搜索树默认成员函数
  • 📜3. 二叉搜索树模拟实现(递归)
    • 🌞插入
    • 🌙查找
    • ⭐删除
  • 📚4. 二叉搜索树的应用
    • 🍁KV模型
    • 🍂KV模型实现
      • 💧英汉词典
      • 🔥计数
    • 🌄二叉树巩固知识
  • 📖5. 总结


前言: 在数据结构和算法的广阔领域中,二叉搜索树(Binary Search Tree,简称BST)无疑是一颗璀璨的明星。它以其高效的数据检索能力和独特的树形结构,在计算机科学领域扮演着举足轻重的角色。对于任何对编程和数据结构感兴趣的人来说,掌握二叉搜索树都是至关重要的一步

二叉搜索树不仅仅是一个简单的数据结构,它更是一种解决问题的方式和思维的体现。通过维护二叉树中每个节点的左子树所有值均小于它的值,右子树所有值均大于它的值的特性,二叉搜索树在插入、查找和删除操作中展现出了卓越的性能。这种特性使得二叉搜索树在各种应用中成为了一种理想的数据结构选择,从基础的算法练习到复杂的系统优化,都能见到它的身影

学习二叉搜索树并非易事。它需要我们深入理解其性质、原理和算法实现。我们需要掌握如何构建一棵二叉搜索树,如何遍历它,以及如何在其中进行高效的查找、插入和删除操作。这些都需要我们付出大量的时间和精力去学习和实践。我们将从二叉搜索树的基本概念出发,逐步深入到其性质、构建、遍历以及操作的实现

让我们一起踏上学习二叉搜索树的旅程,探索它带来的无尽可能!
(本文重在二叉搜索树的模拟实现与理解)


📒1. 二叉搜索树

🎩二叉搜索树概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

在这里插入图片描述


🎈二叉搜索树操作

首先,在二叉搜索树的操作中只支持插入,查找,删除,遍历,并不支持修改操作,因为在修改后谁也不能保证它依然是一棵二叉搜索树,二叉搜索树的时间复杂度范围在(O(logN)~O(N))

在二叉搜索树的遍历中一般采用中序遍历: 先遍历左子树,然后访问根节点,最后遍历右子树。在BST中,中序遍历会按照升序访问所有节点

二叉搜索树示例
在这里插入图片描述

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

📕2. 二叉搜索树模拟实现

🧩二叉搜索树结构

在这里插入图片描述

二叉搜索树结构的和树形结构差不多,这意味着每个元素(通常称为节点)都有两个指针:一个指向前一个左子树,另一个指向右子树,因此我们需要单独再定义一个类来表示节点结构,每个节点再串联起来构成BST

(在模拟实现二叉搜索树时,不用定义命名空间,因为不会和库中发生冲突)

节点定义(示例):

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

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

BST定义(示例):

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 构造函数等可能的其他成员函数... 
private:
	Node* _root = nullptr;
};

🧩二叉搜索树操作

🌈插入

插入的具体过程如下:

  • 树为空,则直接新增节点,赋值给root指针
  • 树不空,按二叉搜索树性质查找插入位置,插入新节点

在这里插入图片描述
插入代码实现(示例):

bool Insert(const K& key)
{
	// 当根为空时,直接插入
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	// 定义parent是因为,在最后找到插入位置时,需要parent将节点进行连接
	Node* parent = nullptr; 
	Node* cur = _root;
	while (cur)
	{
		parent = cur;
		// 插入的值比cur位置大时,cur往右移动
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		// 插入的值比cur位置小时,cur往左移动
		else if (key < cur->_key)
		{
			cur = cur->_left;
		}
		// 当插入的值和cur位置相等时,直接退出,因为二叉搜索树不允许有相同的元素
		else
		{
			return false;
		}
	}
	// 将插入位置的新节点与二叉搜索树连接
	cur = new Node(key);
	if (parent->_key < key)
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

🌞遍历

在二叉搜索树的遍历上,我们依旧采用当初二叉树时的中序遍历,但是我们想要递归遍历就必须调用节点,这里我们要调用两层

遍历代码实现(示例):

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

void _InOrder(Node* root)
{
	// 递归出口
	if (root == nullptr)
	{
		return;
	}
	_InOrder(root->_left);
	cout << root->_key << " " ;
	_InOrder(root->_right);
}

遍历比较简单奥,我们接着往下讲


🌙查找

二叉搜索树的查找

  • 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找
  • 最多查找高度次,走到到空,还没找到,这个值不存在

查找代码实现(示例):

bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		// 查找的值比cur大,cur往右移动
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		// 查找的值比cur小,cur往左移动
		else if (key < cur->_key)
		{
			cur = cur->_left;
		}
		// 找到key,返回true
		else
		{
			return true;
		}
	}
	return false; // 找不到key,返回false
}

⭐删除

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

  • 要删除的结点无孩子结点
  • 要删除的结点只有左孩子结点
  • 要删除的结点只有右孩子结点
  • 要删除的结点有左、右孩子结点

而上面四种情况可以转化成下面的情况:

  • 删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点–直接删除
  • 删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点–直接删除
  • 在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题–替换法删除

在这里插入图片描述

这三种情况就是我们模拟实现删除的方法!

删除代码实现(示例):

bool Erase(const K& key)
{
	Node* cur = _root;
	Node* parent = nullptr;
	while (cur)
	{
		parent = cur;
		if (key > cur->_key)
		{
			cur = cur->_right;
		}
		else if (key < cur->_key)
		{
			cur = cur->_left;
		}
		else
		{
			// 准备删除
			// 左为空
			if (cur->_left == nullptr)
			{
				// 当需要删除的是头节点时
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}
			}
			// 右为空
			else if (cur->_right == nullptr)
			{
				// 当需要删除的是头节点时
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					if (cur == parent->_left)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}
			}
			// 两边都不为空
			else
			{
				// 这里与外层不是同一块作用域,所以可以再次定义parent,不把parent定义为nullptr是因为
				//,可能不进入下面循环导致对parent空指针的使用
				Node* subLeft = cur->_right; // 定义右数节点
				Node* parent = cur;

				while (subLeft->_left)
				{
					parent = subLeft;
					subLeft = subLeft->_left;
				}
				swap(cur->_key, subLeft->_key); // 替换右子树的最左节点
				if (subLeft == parent->_left)
				{
					// 最左节点肯定没有左孩子,所以让parent接管它的右子树
					parent->_left = subLeft->_right;
				}
				else
				{
					parent->_right = subLeft->_right;
				}
				delete subLeft;
			}
			return true;
		}
	}
	return false;
}

🧩二叉搜索树默认成员函数

构造

BSTree() = default; // 显式地定义默认构造函数  

拷贝构造

BSTree(const BSTree<K>& t)
{
	_root = Copy(t._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;
}

赋值重载

BSTree<K>& operator=(BSTree<K> t)
{
	// 现代写法-> 直接调用swap
	swap(_root, t._root);
	return *this;
}

析构

~BSTree()
{
	Destory(_root);
}

void Destory(Node*& _root)
{
	if (_root == nullptr)
	{
		return;
	}
	// 递归调用析构
	Destory(_root->_left);
	Destory(_root->_right);
	delete _root;
	
    _root == nullptr;
}

📜3. 二叉搜索树模拟实现(递归)

在进行递归操作的模拟实现时,一般都要传节点,进行多层的调用,因为我们都要定义两层

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

🌞插入

代码实现(示例):

bool _InsertR(Node*& _root, const K& key)
{
	// 递归出口
	if (_root == nullptr)
	{
		// 这里我们无需在进行对新节点的连接,因为我们是传引用传参,
		_root = new Node(key);
		return true;
	}

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

⭐删除

代码实现(示例):

bool _EraseR(Node*& _root, const K& key)
{
	if (_root == nullptr)
	{
		return false;
	}
	if (key > _root->_key)
	{
		return _EraseR(_root->_right, key);
	}
	else if (key < _root->_key)
	{
		return _EraseR(_root->_left, key);
	}
	else
	{
		// 删除
		if (_root->_left == nullptr)
		{
			Node* del = _root;
			_root = _root->_right;
			delete del;
			return true;
		}
		else if (_root->_right == nullptr)
		{
			Node* del = _root;
			_root = _root->_left;
			delete del;
			return true;
		}
		else
		{
			Node* subLeft = _root->_right;
			while (subLeft->_left)
			{
				subLeft = subLeft->_left;
			}
		swap(_root->_key, subLeft->_key);
		// 让子树继续递归下去
		return _EraseR(_root->_right, key);
		}
	}
}

📚4. 二叉搜索树的应用

🍁KV模型

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

  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
    文单词与其对应的中文<word, chinese>就构成一种键值对
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
    现次数就是<word, count>就构成一种键值对
namespace kv // 避免与之前k模型冲突
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K>* _left;
		BSTreeNode<K>* _right;
		K _key;
		V _value;
		
		BSTreeNode(const K& key = K(), const V& value = V())
			: _left(nullptr)
			, _right(nullptr)
			, _key(key)
			, _value(value)
		{}
	};
	template<class K, class V>
	class BSTree
	{
		typedef BSTreeNode<K, V> Node;
	public:
		// 构造函数等可能的其他成员函数... 
		// 在成员函数中,我们只需要在insert中加入value元素即可
	private:
		Node* _root = nullptr;
	};
}

在成员函数中,我们只需要在Insert中加入value元素即可

🍂KV模型实现

💧英汉词典

代码实现(示例):

int main()
{
	kv::BSTree<string, string> dict;
	dict.insert("left", "左边、剩余");
	dict.insert("string", "字符串");
	dict.insert("hahaha", "哈哈");
	dict.insert("Eternity", "永恒");
	dict.insert("sort", "排序");
	dict.InOrder();
	string str;
	while (cin >> str)
	{
		kv::BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "无此单词" << endl;
		}
	}
}

🔥计数

代码实现(示例):

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	kv::BSTree<string, int> countTree;
	for (auto& e : arr)
	{
		kv::BSTreeNode<string, int>* ret = countTree.Find(e);
		if (ret == nullptr)
		{
			countTree.insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
	return 0;
}

🌄二叉树巩固知识

最后在这给大家推荐几道巩固二叉树的编程题
二叉树创建字符串
二叉树的分层遍历
找到树中两个指定节点的最近公共祖先
二叉树搜索树转换成排序双向链表
根据一棵树的前序遍历与中序遍历构造二叉树
二叉树中序遍历 ,非递归迭代实现


📖5. 总结

经过我们一同对搜索二叉树的深入学习和探索,相信你已经对这种数据结构有了全面而深刻的理解。搜索二叉树以其独特的性质在数据检索领域展现了出色的性能,无论是插入、删除还是查找操作,都体现了其高效和灵活的特点

学习的道路永无止境。虽然我们已经走过了搜索二叉树的基本概念和操作的学习之旅,但这只是你编程旅程中的一个站点。前方还有更多数据结构等待你去探索,更多算法等待你去实践

不要忘记持续学习和自我提升。计算机科学是一个日新月异的领域,新的技术和算法不断涌现。只有保持对知识的渴望和追求,我们才能在这个领域中不断前行,让我们一起在学习的道路上不断前行!
在这里插入图片描述
希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学习笔记——路由网络基础——路由度量值

3、路由度量值 (1)基本概念 路由度量值表示到达这条路由所指目的地址的代价。度量值数值越小越优先&#xff0c;度量值最小路由将会被添加到路由表中。度量值很多时候被称为开销(Cost)。 路由度量(路由开销 cost)对于同一个路由协议&#xff0c;当到达某目标网段有多条路由供…

适配不同数据库厂商方案

背景 在对国产化数据有要求的时候&#xff0c;我们会做对 达梦、海量等数据库的配置。 有些SQL 以前没有写成标准SQL&#xff1b; 那么适配的时候怎么办呢&#xff1f;改成标准SQL。 如果不好改呢&#xff1f;比如SQL比较复杂等&#xff0c;需要判断 当前是哪个厂商的数据库…

HTML星空特效

目录 写在前面 完整代码 代码分析 运行效果 系列文章 写在后面 写在前面 100行代码实现HTML星空特效。 完整代码 全部代码如下。 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&g…

数据结构与算法1

一、概述 数据结构&#xff08;逻辑结构、存储结构、算法&#xff09; 数据项 ∈ 数据元素(记录) ∈ 数据。 数据元素&#xff08;结点&#xff09;&#xff1a;数据的基本单位。数据项&#xff1a;不可分割&#xff0c;最小数据单位。数据对象 &#xff1a;性质相同的数据元素…

HTTP学习记录(基于菜鸟教程)

文章目录 1.简介1.1常用的HTTP方法1.2Http版本1.3注意事项 2.Https3.Http消息结构3.1客户端请求消息3.2响应消息 4.常见的响应头5.HTTP状态码6.Http content-type在这里插入图片描述 7.MIME类型8.HTTP2 1.简介 Http&#xff0c;被称为超文本传输协议&#xff0c;HyperText Tran…

RK3588 Android12音频驱动分析全网最全

最近没有搞音频相关的了&#xff0c;在搞BMS, 把之前的经验总结一下。 一、先看一下Android 12音频总架构 从这张图可以看到音频数据流一共经过了3个用户空间层的进程&#xff0c;然后才流到kernel驱动层。Android版本越高&#xff0c;通用性越高&#xff0c;耦合性越低&#…

python flask 入门-helloworld

学习视频链接&#xff1a; 01-【前奏】课程介绍_哔哩哔哩_bilibili 1.安装flask pip install flask 踩坑记&#xff1a;本机不要连代理&#xff0c;否则无法install 提示报错valueError: check_hostname requires server_hostname 2.程序编写 在根目录下创建 app.py fr…

尚硅谷爬虫学习第一天(2) 爬虫案例

import urllib.request# 下载网页 url_page http://www.baidu.com # url 代表下载的路径&#xff0c;filename 代表文件的名字 # urllib.request.urlretrieve(url_page,baidu.html) # 在python中 可以写变量的名字&#xff0c;也可以直接写值,这不就是java吗# 下载图片 # url_…

Mybatis(根据id查找这一行的数据)

首先在查询之前&#xff0c;我们先要做些基础的工作先创建一个以你的数据库命名的model类 我的数据库的名字叫admin 我就创建了一个Admin的类 用来方便数据的访问 然后我们就要创建一个接口来声明我们要写的方法 我创建的接口命名为AdminDao 在创建一个xml的类用来实现声明的…

把Deepin塞进U盘,即插即用!Deepin To Go来袭

前言 小伙伴之前在某篇文章下留言说&#xff1a;把Deepin塞进U盘的教程。 这不就来了吗&#xff1f; 事实是可以的。这时候你要先做点小准备&#xff1a; 一个大小为8GB或以上的普通U盘 一个至少64GB或以上的高速U盘 一个Deepin系统镜像文件 普通U盘的大概介绍&#xff1…

Flink 资源静态调度

本内容是根据 Flink 1.18.0-Scala_2.12 版本源码梳理而来。本文主要讲述任务提交时&#xff0c;为 Task 分配资源的过程。 以下是具体步骤讲解&#xff1a; TaskManager 资源注册 TaskManager 在启动时&#xff0c;会向 ResourceManager 注册资源。ResourceManager 会将 Tas…

AI 代理可以改变 B2B 电子商务的业务动态

今天你听到的都是人工智能&#xff0c;这是有原因的。在过去 18 个月里&#xff0c;我们经历了比以往更多的人工智能创新。人工智能一夜之间走出了实验室&#xff0c;并成为可行的商业驱动力。 一个有望赢得巨大胜利的行业是 B2B电子商务。事实上&#xff0c;B2B 电子商务可以…

2021 hnust 湖科大 C语言课程设计报告+代码+流程图源文件+指导书

2021 hnust 湖科大 C语言课程设计报告代码流程图源文件指导书 目录 报告 下载链接 https://pan.baidu.com/s/14NFsDbT3iS-a-_7l0N5Ulg?pwd1111

嵌入式实验---实验二 中断功能实验

一、实验目的 1、掌握STM32F103中断程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、在上一章的实验基础上&#xff0c;添加一个按键和一个LED&#xff1b; 2、使用中断的方式实现以下两个功能&#xff1a; &#xff08;1&#xff09;KEY1按键…

计算机图形学入门16:曲线

1.曲线 曲线&#xff08;Curves&#xff09;在图形学中应用非常广泛&#xff0c;比如&#xff1a;相机的拍摄路径、物体的移动路径、动画曲线、矢量字体等。如下图所示&#xff0c;是使用曲线到矢量字体的应用&#xff0c;通过移动一些控制点来改变字体。 2.贝塞尔曲线 2.1 贝…

[Vulnhub]Wintermute LFI+SMTP+Screen+Structv2-RCE+Lxc逃逸

概要 靶机 192.168.8.104 信息收集 $ nmap 192.168.8.103 --min-rate 1000 -sC -sV 结果: Starting Nmap 7.92 ( https://nmap.org ) at 2024-06-15 05:54 EDT Nmap scan report for 192.168.8.103 (192.168.8.103) Host is up (0.035s latency). Not shown: 997 closed t…

《软件定义安全》之七:SDN安全案例

第7章 SDN安全案例 1.DDoS缓解 1.1 Radware DefenseFlow/Defense4All Radware在开源的SDN控制器平台OpenDaylight&#xff08;ODL&#xff09;上集成了一套抗DDoS的模块和应用&#xff0c;称为Defense4ALL。其架构如下图&#xff0c;主要有两部分&#xff1a;控制器中的安全…

web安全渗透测试十大常规项(一):web渗透测试之XML和XXE外部实体注入

#详细点: XML被设计为传输和存储数据,XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素,其焦点是数据的内容,其把数据从HTML分离,是独立于软件和硬件的信息传输工具。等同于JSON传输。XXE漏洞XML External Entity Injection,即xml外部实体注入漏洞,XXE漏洞发…

windows10安装paraview

下载软件&#xff1a; https://www.paraview.org/download/ 下载如下版本即可&#xff1a;

跟《经济学人》学英文:2024年6月15日这期 Chinese electric vehicles (EVs)

The EU hits China’s carmakers with hefty new tariffs Duties will only hold them back for a while 欧盟对中国汽车制造商征收高额新关税 hit: 对xxx施加 在句子"The EU hits China’s carmakers with hefty new tariffs"中&#xff0c;“hits”的意思是“对…