【C++】——二叉搜索树(详解)

news2024/11/25 23:47:18

一  二叉搜索树概念

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

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

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

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

二  二叉搜索树的操作 

和其他结构差不多,一般都为增删查改

2.1  二叉搜索树的查找

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

比如我们要查找6,那么就会先跟8比较,然后往左边走,比3大,就往右边走,然后就找到了所对应的值。

2.2  二叉搜索树的插入

插入的具体过程如下:
a. 树为空,则直接新增节点,赋值给root指针
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点

 对于插入来说,也就是比查找多了一个插入的过程

2.3  二叉搜索树的删除

对于删除来说就很有讲究了,因为你要保证它删除完以后还是一个二叉搜索树,不然就扯蛋了

  ✨首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
1. 要删除的结点无孩子结点
2. 要删除的结点只有左孩子结点
3. 要删除的结点只有右孩子结点
4. 要删除的结点有左、右孩子结点


看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程
如下:
情况2:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点--直接删除
情况3:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点--直接删除
情况4:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点
中,再来处理该结点的删除问题--替换法删除

2.3 二叉搜索树的应用

K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到
的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
        以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
        在二叉搜索树中检存在则拼写正确,不存在则拼写错误。

2. KV模型:每一个关键码key,都有与之对应索该单词是否存在,的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
         比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英
         文单词与其对应的中文<word, chinese>就构成一种键值对;
         再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出
         现次数就是<word, count>就构成一种键值对。

对于容器map和set的底层就是二叉搜索树的优化,所以二叉搜索的应用是非常广泛的。

2.4  二叉搜索树的性能

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

其实从表面上看二叉搜索树的效率是很高的,但是它也有极端情况

最优情况是第一个,类似完全二叉树,平均比较次数为lon(N),

最坏情况是第二个图,二叉搜索树退化为单支树(或者类似单支)

如果是第二个那么就让搜索二叉树没有了优势,但是后面会有对这个的改良。

三  二叉搜索树的实现

3.1  二叉搜索树结构

首先我们肯定得有一个结点用来存储内部结构

template<class T>
class BSTreeNode
{
	BSTreeNode<T> *left;//左指针
	BSTreeNode<T> *right;//右指针
	T val;

	BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr)
	{
	}
};

其次我们需要的一颗树,对于二叉搜索树来说,我们主要是删除和插入的操作,其他的其实都大同小于

template <class T>
class BSTree
{
	typedef BSTreeNode<T> Node;
    public:
    bool  insert(const T&key);
    bool  Find(const T&key);
    bool  Erase(const T&key);
    private:
    Node* root;
};

3.2   插入元素

对于插入元素,我们需要的是先找到那个可以插入的值,如果要插入的值已经存在,那么就插入失败,如果不存在,那么我就必须按照二叉搜索树的规则进行插入

如果不懂,那看看代码就应该会清晰了

bool insert(const T& key)
	{
		if (root == nullptr)//特判,如果是跟,那么直接插入不需要去找
		{
			root = new Node(key);
			return true;
		}
		Node* cur = root;
		Node* parent = nullptr;//以便我们找到父亲结点
		while (cur)
		{
			if (key>cur->val)//如果插入值大于当前值,往右走
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)//同理,往左走
			{
				parent = cur;
				cur = cur->left;
			}
			else//相等,说明已经存在了,插入失败
			{
				return false;
			}
		}

        //这里说明找到位置了进行插入
		cur = new Node(key);
		if (parent -> val<key)//插入之前也要判断是插入到左边还是右边
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}
		return true;
	}

以上就是插入的代码,总结下来就是,先特判,然后去找位置,找到位置进行插入,插入的时候也要判断是插左边还是右边

3.3  寻找元素

寻找元素就是插入元素的简单版本,思路就是插入元素上面的,下面给出代码

bool Find(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->val)
			{
				cur = cur->right;
			}
			else if (key < cur->val)
			{
				cur = cur->left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

3.4  删除元素

对于删除元素,里面的细节就很多了

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

下面我们就根据这些操作一步一步去完成

首先我们肯定是先去寻找要删除的元素在哪里

bool Erase(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->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* parent = cur;//这里需要把parent置成cur
					Node* subLeft = cur->right;//这里我们去右边找最小的,也就是右边最左值
					while (subLeft->left)
					{
						parent = subLeft;
						subLeft = subLeft->left;
					}
					swap(cur->val, subLeft->val);//找到了交换
					if (parent->left == subLeft)//然后判断是在父亲的左边还是右边
					{
						parent->left = subLeft->right;//因为是最左边,所以他只会有右子树
					}
					else if (parent->right = subLeft)
					{
						parent->right = subLeft - right;
					}
					delete subLeft;//链接上了,就直接删除
				}
				return true;
			}
		}
		return false;
	}

 

以上就是二叉搜索树的最关键的操作函数实现,其实不难,注意细节就行了

完整代码

#pragma once
template<class T>
class BSTreeNode
{
	BSTreeNode<T> *left;
	BSTreeNode<T>* right;
	T val;

	BSTreeNode(const T&key):val(key),left(nullptr),right(nullptr)
	{
	}
};

template <class T>
class BSTree
{
	typedef BSTreeNode<T> Node;
public:
	bool insert(const T& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key>cur->val)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
			{
				return false;
			}
		}
		cur = new Node(key);
		if (parent -> val<key)
		{
			parent->right = cur;
		}
		else
		{
			parent->left = cur;
		}
		return true;
	}

	bool Find(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->val)
			{
				cur = cur->right;
			}
			else if (key < cur->val)
			{
				cur = cur->left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}

	bool Erase(const T& key)
	{
		if (root == nullptr) return false;
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (key > cur->key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (key < cur->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* parent = cur;
					Node* subLeft = cur->right;
					while (subLeft->left)
					{
						parent = subLeft;
						subLeft = subLeft->left;
					}
					swap(cur->val, subLeft->val);
					if (parent->left == subLeft)
					{
						parent->left = subLeft->right;
					}
					else if (parent->right = subLeft)
					{
						parent->right = subLeft - right;
					}
					delete subLeft;
				}
				return true;
			}
		}
		return false;
	}
	void InOder()
	{
		_InOder(root);
		cout << endl;
	}

private:
	
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr) return;
		if (root->val < key)
		{
			return _EraseR(root->right, key);
		}
		else if (root->val > 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(subLeft->val, root->val);
				return _EraseR(root->right, key);
			}
		}
	}



	void _InOder(Node*root)
	{
		if (root == nullptr)
			return;
		_InOder(root->left);
		cout << root->val << ' ';
		_InOder(root->right);
	}
	Node* root=nullptr;
};

四  二叉搜索树递归实现

对于二叉搜索树的递归实现,这里着重去解释删除操作的递归

bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr) return;
		if (root->val < key)
		{
			return _EraseR(root->right, key);/递归去找删除的数
		}
		else if (root->val > 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(subLeft->val, root->val);
				return _EraseR(root->right, key);//这里递归去右树删除,这样就省去很多麻烦
			}
		}

从上面代码中我们可以看出,其实少了很多的判断,尤其是父亲结点的链接判断,之所以会这样,就是因为我们用了引用这样可以直接改变指针指向的位置,在连接的时候,就直接连接上了,自然就不需要父亲结点。

有了上面删除的递归版本,那么其他的理解起来就很容易了

bool _InsertR(Node*& root, const K& key)
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->val < key)
			return _InsertR(root->right, key);
		else if (root->val > key)
			return _InsertR(root->left, key);
		else
			return false;
	}

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

		if (root->val < key)
		{
			return _FindR(root->right, key);
		}
		else if (root->val > key)
		{
			return _FindR(root->left, key);
		}
		else
		{
			return true;
		}
	}

五 总结

以上就是搜索二叉树的大概内容了,希望对你有用

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

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

相关文章

累积阅读量高达1个亿了,刚好完成了一个小目标

大家好&#xff0c; 我是老洪。 做自媒体&#xff0c;这不仅仅是一个职业选择&#xff0c;更是我生活中不可或缺的一部分。 自从我踏入这个领域&#xff0c;时光如白驹过隙&#xff0c;转眼间已经走过了一段不短的旅程。 今天&#xff0c;当我打开后台数据&#xff0c;看到那累…

Benchmarking Panoptic Scene Graph Generation (PSG), ECCV‘22 场景图生成,利用PSG数据集

2080-ti显卡复现 源代码地址 Jingkang50/OpenPSG: Benchmarking Panoptic Scene Graph Generation (PSG), ECCV22 (github.com) 安装 pytorch 1.7版本 cuda10.1 按照readme的做法安装 我安装的过程如下图所示,这个截图是到了pip install openmim这一步 下一步 下一步 这一步…

【单片机毕业设计选题24024】-房间自动除湿控制系统

系统功能: 系统分为手动和自动模式&#xff0c;上电默认为自动模式。自动模式下如果获取到湿度 值大于设定的湿度值则自动打开风扇&#xff0c;手动模式下手动开关风扇。 系统上电后显示“欢迎使用除湿控制系统请稍后”&#xff0c;两秒钟后进入主页面显示。 第一行显示系统…

【漏洞复现】Rejetto HTTP File Server 远程代码执行漏洞 (CVE-2024-23692)

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

软件测试----用例篇(设计测试用例保姆级教程✅)

文章目录 前言一、测试用例概念 二、如何设计测试用例三、设计测试用例的方法3.1基于需求的设计方法3.2具体的设计方法等价类边界值正交法判定表法场景法错误猜测法 前言 在软件开发过程中&#xff0c;测试用例是至关重要的一环。它们帮助软件开发人员和测试人员确定软件是否按…

Day2: 双指针977 滑动窗口209 循环不变量原则59

题目977. 有序数组的平方 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> sortedSquares(vector<int>& nums) {int left0;int rightnums.size()-1;vector<int> result(nums.size(),0);int iright;while(left<right){i…

1999-2022年 297个地级市-医院卫生院数量及床位数量(数据收集)

全国297个地级市的医院卫生院数量的稳步增长是医疗事业发展的一个重要标志。政府的持续投入和对医疗设施的改善&#xff0c;不仅提升了医疗服务的硬件水平&#xff0c;也通过引进和培养医疗人才、优化服务流程&#xff0c;提高了医疗服务的整体质量。这些举措极大地增强了人民群…

[Open-source tool]Uptime-kuma的簡介和安裝於Ubuntu 22.04系統

[Uptime Kuma]How to Monitor Mqtt Broker and Send Status to Line Notify Uptime-kuma 是一個基於Node.js的開軟軟體&#xff0c;同時也是一套應用於網路監控的開源軟體&#xff0c;其利用瀏覽器呈現直觀的使用者介面&#xff0c;如圖一所示&#xff0c;其讓使用者可監控各種…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《基于已知电网场景分段拟合智能体智能评估与自主进化方法 》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

技术师增强版,系统级别的工具!【不能用】

数据安全是每位计算机用户都关心的重要问题。在日常使用中&#xff0c;我们经常面临文件丢失、系统崩溃或病毒感染等风险。为了解决这些问题&#xff0c;我们需要可靠且高效的数据备份与恢复工具。本文将介绍一款优秀的备份软件&#xff1a;傲梅轻松备份技术师增强版&#xff0…

构建家庭NAS之一:用途和软硬件选型

用途 最近装了一台NAS&#xff0c;把结果记录一下&#xff0c;也给有兴趣的人做个参考。 我原来有一台基于英特尔Atom 525的NAS&#xff0c;一直勤勤恳恳地正常服役&#xff0c;突然有一天毫无征兆地挂了&#xff0c;只能换一台新的。 我的用途很简单&#xff1a; 备份我和…

简单了解html常用的标签

HTML 一、基础认知 1、注释 1.1、注释的作用和写法 1.1.1、作用 为代码添加解释性&#xff0c;描述性的信息&#xff0c;主要用来帮助开发人员理解代码&#xff0c;浏览器执行代码时回忽略所有注释。 1.1.2、注释的快捷键 在VS Code中&#xff1a;Ctrl / 2、HTML标签的…

程序猿大战Python——面向对象——魔法方法

什么是魔法方法&#xff1f; 目标&#xff1a;了解什么是魔法方法&#xff1f; 魔法方法指的是&#xff1a;可以给Python类增加魔力的特殊方法。有两个特点&#xff1a; &#xff08;1&#xff09;总是被双下划线所包围&#xff1b; &#xff08;2&#xff09;在特殊时刻会被…

Clonable接口和拷贝

Hello~小伙伴们&#xff01;本篇学习Clonable接口与深拷贝&#xff0c;一起往下看吧~(画图水平有限&#xff0c;两张图&#xff0c;&#xff0c;我真的画了巨久&#xff0c;求路过的朋友来个3连~阿阿阿~~~) 目录 1、Clonable接口概念 2、拷贝 2、1浅拷贝 2、2深拷贝 1、Clon…

利用竞争智慧与大型语言模型:假新闻检测的新突破

Explainable Fake News Detection With Large Language Model via Defense Among Competing Wisdom 论文地址: Explainable Fake News Detection with Large Language Model via Defense Among Competing Wisdom | Proceedings of the ACM on Web Conference 2024https://dl.…

国企:2024年6月中国移动相关招聘信息 二

在线营销服务中心-中国移动通信有限公司在线营销服务中心 硬件工程师 工作地点:河南省-郑州市 发布时间 :2024-06-18 截至时间: 2024-06-30 学历要求:本科及以上 招聘人数:1人 工作经验:3年 岗位描述 1.负责公司拾音器等音视频智能硬件产品全过程管理,包括但…

【前端vue3】TypeScrip-Class类用法

类型声明 TypeScrip定义Class类 语法&#xff1a; // 定义一个名为 Person 的类 class Person {constructor () {// 构造函数&#xff1a;稍后定义}run () {// 方法&#xff1a;稍后定义} }在TypeScript是不允许直接在constructor 定义变量的 需要在constructor上面先声明 例…

【递归、搜索与回溯】floodfill算法一

floodfill算法一 1.floodfill算法简介2.图像渲染3.岛屿数量4.岛屿的最大面积 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.floodfill算法…

AI视频教程下载-与ChatGPT结合的UX用户体验/UI用户界面设计策略

Revolutionize UX_UI_ AI-Design Strategies with ChatGPT 提升你的设计工具包&#xff1a;使用ChatGPT、Figma和Miro的AI驱动UX/UI策略 50个创新UX提示 了解人工智能的基础知识。介绍ChatGPT及其底层技术。区分不同AI模型及其在设计中的应用。将AI工具融入设计工作流程的策略…

摄像头画面显示于unity场景

&#x1f43e; 个人主页 &#x1f43e; &#x1faa7;阿松爱睡觉&#xff0c;横竖醒不来 &#x1f3c5;你可以不屠龙&#xff0c;但不能不磨剑&#x1f5e1; 目录 一、前言二、UI画面三、显示于场景四、结语 一、前言 由于标题限制&#xff0c;这篇文章主要是讲在unity中调用摄…