【C++】搜索二叉树

news2024/11/24 5:41:17

文章目录

  • 📕 概念
  • 📕 搜索二叉树的实现
    • 框架
    • 插入节点
    • 查找节点
    • ★ 删除节点 ★
  • 📕 源代码

📕 概念

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

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

请添加图片描述

📕 搜索二叉树的实现

框架

搜索二叉树也是一个二叉树,所以,要先用结构体定义树的节点,如下的 struct BSTreeNode,结构体里面包含左右指针以及值,当然了,也需要构造函数,因为 new 一个节点的时候是需要传参的。


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

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

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    // 接口
private:
	Node* _root = nullptr;
};

插入节点

插入的分类如下:

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

如下,当树不为空的时候,要找到需要插入节点的位置。

  • 如果当前节点的值,大于要插入的节点的值,那么当前节点往其左子节点移动。
  • 如果当前节点的值,小于要插入的节点的值,那么当前节点往其右子节点移动。
  • 如果当前节点的值,等于要插入的节点的值,说明该树里面已存在要插入的值的节点,插入失败返回 false。

但是,即使找到了需要插入的节点位置,也需要将其链接到父节点上,由于我们只找到了需要插入到哪个位置,并不知道其父节点,所以,需要定义一个指针变量 parent 来指向其父节点。

现在已经找到了需要插入的节点位置 cur ,以及其父节点 parent,其余信息我们是不知道的。比如,要插入的节点的值是比 parent 的值大还是小?所以这里需要判断一下,然后作链接。

bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur) // 找到要插入的位置
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}

		if (parent->_key < val)
			parent->_right = new Node(val);
		else
			parent->_left = new Node(val);

		return true;
	}

查找节点

查找节点很容易,当前节点的值比目标值大,那么当前节点就变成其左子节点;当前节点的值比目标值小,当前节点就变成其右子节点;否则就是相等,表示找到了。

当然,如果当前节点成了 nullptr ,那么就是没有找到。

	bool find(const K& val)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > val)
			{
				cur = cur->_left;
			}
			else if (cur->_key < val)
			{
				cur = cur->_right;
			}
			else
				return true;
		}
		return false;
	}

★ 删除节点 ★

要删除节点,首先需要找到这个节点,找到之后,再考虑删除的事情。

删除的情况也分为多种,因为待删除节点挂接的情况有所不同

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

但是,对于前三种情况,实际上可以合为一种类型。
如下,当要删除的节点无孩子节点,直接删除,然后父节点的对应子节点置为空。
当要删除的节点有一个子节点,那么把节点删除后,将被删除节点 的 非空的子节点链接到父节点。

可以归为一类的原因是,如下,删除 13 的时候,这个节点没有子节点,即左右子节点指针指向 nullptr,将 13 删除之后,父节点的左子节点也要指向 nullptr,这时可以理解为,将 13 的其中一个子节点链接到父节点的左子节点
请添加图片描述

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

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

当要删除的节点,左右子树都存在的时候,删除起来是较为麻烦的。如下图,删除 3 这个节点,此时 3 存在左右子树,那么就需要找一个节点来替代 3 这个节点。同时,要使得替代 3 的节点,也要满足搜索二叉树的性质。
那么,就需要找到 3 的左子树中,最大的节点(该节点比 3 的左子树中其他节点都大,比 3 的右子树节点都小,适合替代 3 这个节点);或者是找到 3 的右子树中,最小的节点(理由和之前类似)。任意找这两个节点之一,替代 3 这个节点即可。以右子树的最小节点为例,下图中是 4 这个节点,但是 4 这个节点也有右子树(不会有左子树,如果有, 4 就不是最小节点),所以要将其右子树链接到 4 的父节点。

请添加图片描述

但是,如果右子树的最小节点,就是要删除节点的右节点呢?如下,例如要删除 8 ,此时去寻找 8 的右子树的最小节点,毫无疑问是 14 这个节点,此时需要单独判断一下,因为 parent 节点是 nullptr 。

请添加图片描述

如下,删除节点的代码。

	bool Erase(const K& val)
	{
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 相等,删除
			{
				// 左为空
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
				}// 右为空
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					Node* pminRight = nullptr;
					Node* minRight=cur->_right;
					while (minRight->_left)
					{
						pminRight = minRight;
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;
					// 删除节点的右边就是最小值
					if (minRight == cur->_right)
					{
						cur->_right = minRight->_right;
					}
					else 
					{
						pminRight->_left = minRight->_right;
					}
					
					delete minRight;
				}
				return true;
			}

		}
		return false;
	}

📕 源代码

搜索二叉树代码如下,其递归法实现 插入、查找、删除等等,思路是一样的。


#pragma once
#include<algorithm>

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

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

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:

	bool Insert(const K& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(val);
			return true;
		}
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else {
				return false;
			}
		}

		if (parent->_key < val)
			parent->_right = new Node(val);
		else
			parent->_left = new Node(val);

		return true;
	}

	bool find(const K& val)
	{
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key > val)
			{
				cur = cur->_left;
			}
			else if (cur->_key < val)
			{
				cur = cur->_right;
			}
			else
				return true;
		}
		return false;
	}


	// 删除
	bool Erase(const K& val)
	{
		Node* cur = _root;
		Node* parent = _root;
		while (cur)
		{
			if (cur->_key < val)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > val)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 相等,删除
			{
				// 左为空
				if (cur->_left == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
					delete cur;
				}// 右为空
				else if (cur->_right == nullptr)
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					Node* pminRight = nullptr;
					Node* minRight=cur->_right;
					while (minRight->_left)
					{
						pminRight = minRight;
						minRight = minRight->_left;
					}

					cur->_key = minRight->_key;
					// 删除节点的右边就是最小值
					if (minRight == cur->_right)
					{
						cur->_right = minRight->_right;
					}
					else 
					{
						pminRight->_left = minRight->_right;
					}
					
					delete minRight;
				}
				return true;
			}

		}
		return false;
	}

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

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

	bool FIndR(const K& val)
	{
		return _FindR(_root, val);
	}

	bool _InsertR(Node* &root, const K& val)
	{
		if (root == nullptr)
		{
			root = new Node(val);
			return true;
		}
		if (root->_key < val)
			_InsertR(root->_right, val);
		else if (root->_key > val) 
			_InsertR(root->_left, val);
		else return false;
	}

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

	bool _EraseR(Node* &root, const K& val)
	{
		if (root == nullptr) return false;
		if (root->_key > val)
		{
			_EraseR(root->_left, val);
		}
		else if (root->_key < val)
		{
			_EraseR(root->_right, val);
		}
		else
		{
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			else // 左右都不为空
			{
				Node* maxleft = root->_left;
				while (maxleft->_right)
				{
					maxleft = maxleft->_right;
				}
				swap(root->_key, maxleft->_key);
				_EraseR(root->_left, val);
			}
			return true;
		}

		return false;
	}

	bool EraseR(const K& val)
	{
		return _EraseR(_root, val);
	}

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

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

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

private:
	Node* _root = nullptr;
};

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

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

相关文章

5。STM32裸机开发(2)

嵌入式软件开发学习过程记录&#xff0c;本部分结合本人的学习经验撰写&#xff0c;系统描述各类基础例程的程序撰写逻辑。构建裸机开发的思维&#xff0c;为RTOS做铺垫&#xff08;本部分基于库函数版实现&#xff09;&#xff0c;如有不足之处&#xff0c;敬请批评指正。 &a…

全景描绘云原生技术图谱,首个《云原生应用引擎技术发展白皮书》发布

5月12日&#xff0c;由神州数码主办、北京经开区国家信创园、中关村云计算产业联盟协办的2023通明湖论坛-云原生分论坛在京召开。论坛期间&#xff0c;神州数码联合北京通明湖信息技术应用创新中心、中国信通院和通明智云正式发布了《云原生应用引擎技术发展白皮书》&#xff0…

干货 | 心理学人电脑选购指南来了!

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是喵君姐姐&#xff5e; 当我们在选择电脑时经常会无从下手&#xff0c;不知道该如何才能选择一款既能满足我们的科研需要又具有良好性价比的电脑。 本期我们邀请到了唐仙和梦马来为我们详细解答心理学…

我的Makefile模板

OBJxxxxCFLAGS -g -Wall${OBJ}:${OBJ}.o main.o%*.o:%*.c.PHONY:clean clean:${RM} *.o ${OBJ} core xxxx → xxxx.c xxxx.h main.c 比如&#xff1a; 包含了&#xff1a;

PhotoScan拼接无人机航拍RGB照片

目录 背景 拼接步骤 1.新建并保存项目 2.添加照片 3.对齐照片 4.添加标记&#xff08;Markers&#xff09; 5.添加地面控制点 6.建立批处理任务 7.使用批处理文件进行批处理 8.导出DEM 9.导出DOM 背景 本文介绍使用地面控制点&#xff08;GCPs&#xff09;拼接​​…

Java面试知识点(全)- Java面试基础部分一

Java基础 语法基础 面向对象 封装 利用抽象数据类型将数据和基于数据的操作封装在一起&#xff0c;使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部&#xff0c;尽可能地隐藏内部的细节&#xff0c;只保留一些对外接口使之与外部发生联系。用户无需知道对…

如何解决ChatGPT网络错误的问题,让AI对话更丝滑~

前言 在当今人工智能技术的飞速发展中&#xff0c;ChatGPT 作为一款大型语言模型备受瞩目。近期&#xff0c;其在各大社交媒体平台上的表现更是引来了一片关注之声。无论是与用户进行有趣的对话&#xff0c;还是帮助人们解决实际问题&#xff0c;ChatGPT 展现出了其强大的自然…

谷歌慌了!想发论文得审批,优先开发产品,让OpenAI没得看

来源 | 机器之心 ID | almosthuman2014 众所周知&#xff0c;谷歌就像人工智能领域的「黄埔军校」&#xff0c;自深度学习兴起后培养出了整整一代机器学习研究人员和工程师。很长一段时间里&#xff0c;谷歌就是领先 AI 技术的代名词。 人们已经习惯跟随谷歌的脚步&#xff0c…

操作符(算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符)

目录 算术操作符 移位操作符 移位规则 位操作符 交换两个整形变量的写法 赋值操作符 单目操作符 sizeof和数组的纠缠 和--运算符 多组输入的方案 关系操作符 逻辑操作符 算术操作符 -- 加法操作符&#xff08;&#xff09;&#xff1a;用于将两个值相加。 -- 减法操…

算法修炼之练气篇——练气八层

博主&#xff1a;命运之光 专栏&#xff1a;算法修炼之练气篇 前言&#xff1a;每天练习五道题&#xff0c;炼气篇大概会练习200道题左右&#xff0c;题目有C语言网上的题&#xff0c;也有洛谷上面的题&#xff0c;题目简单适合新手入门。&#xff08;代码都是命运之光自己写的…

cv2BGR转化为RGB

import cv2 import matplotlib.pyplot as plt img cv2.imread(1.png,1)#1加载彩图 0加载灰度图 img2 cv2.cvtColor(img,cv2.COLOR_BGR2RGB)#cv2读取是BGR 如果使用plt包要转换为RGB plt.subplot(1,2,1) plt.imshow(img2) plt.subplot(1,2,2) plt.imshow(img) plt.savefig(&qu…

【Java零基础入门篇】第 ⑥ 期 - 异常处理

博主&#xff1a;命运之光 专栏&#xff1a;Java零基础入门 学习目标 掌握异常的概念&#xff0c;Java中的常见异常类&#xff1b; 掌握Java中如何捕获和处理异常&#xff1b; 掌握自定义异常类及其使用&#xff1b; 目录 异常概述 异常体系 常见的异常 Java的异常处理机制…

【数学】通俗理解泰勒公式(牛顿迭代法有用到)

【数学】通俗理解泰勒公式&#xff08;牛顿迭代法有用到&#xff09; 文章目录 【数学】通俗理解泰勒公式&#xff08;牛顿迭代法有用到&#xff09;1. 介绍2. 通俗理解2.1 近似计算 3. 泰勒公式的推导4. 泰勒公式的定义5. 扩展 — 麦克劳林公式参考 1. 介绍 最近在看一些机器…

java异常的分类(常见的异常类型)

异常的分类 1. 编译时异常 在程序编译期间发生的异常&#xff0c;称为编译时异常&#xff0c;也称为受检查异常(Checked Exception) public class Person {int age;private String name;private String gender;// 想要让该类支持深拷贝&#xff0c;覆写Object类的clone方法即…

C语言—字符函数和字符串函数

字符函数和字符串函数 strlenstrcpystrcatstrcmpstrncpystrncatstrncmpstrstrstrtokstrerrorperror字符分类函数字符转换函数memcpymemmovememmcmpmemset C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串通常放在 常量字符…

ChatGPT最强对手Claude如何无门槛使用?

Claude&#xff0c;一个冉冉升起的新星&#xff0c;由 chatgpt 团队出来的员工开发的&#xff0c;由于他们对模型的一些发展理念不同&#xff0c;单独融资创建了 Claude&#xff0c;总体来说表现可圈可点&#xff0c;但整体看可能还不如 chatgpt4.0。 ChatGPT 眼中的 Claude C…

【产品应用】一体化电机在卡盘设备中的应用

在现代工业生产中&#xff0c;自动化程度的提高和生产效率的提升对于生产设备的要求也越来越高。卡盘设备作为自动化生产线中的重要组成部分&#xff0c;其设计和制造也必须适应现代工业的需求。一体化电机在卡盘设备中的应用&#xff0c;不仅可以提高生产效率和精度&#xff0…

线程状态是五种还是六种

从操作系统层面上描述线程状态 初始状态&#xff1a;仅仅是语言层面创建了线程对象,还没有与操作系统相关联.比如new 了一个Thread对象还没有调用start方法可运行状态&#xff1a;仅仅是语言层面创建了线程对象,还没有与操作系统相关联.比如new 了一个Thread对象还没有调用s…

微服架构基础设施环境平台搭建 -(二)Docker私有仓库Harbor服务搭建

微服架构基础设施环境平台搭建 -&#xff08;二&#xff09;Docker私有仓库Harbor服务搭建 通过采用微服相关架构构建一套以KubernetesDocker为自动化运维基础平台&#xff0c;以微服务为服务中心&#xff0c;在此基础之上构建业务中台&#xff0c;并通过Jekins自动构建、编译、…

【数学杂记】表达式中的 s.t. 是什么意思

今天写题的时候遇见了这个记号&#xff1a;s.t.&#xff0c;查了一下百度。 s.t.&#xff0c;全称 subject to&#xff0c;意思是“使得……满足”。 比如这个&#xff1a; 意思是存在 i i i&#xff0c;使得 i i i 满足 A i ≠ B i A_i\neq B_i Ai​Bi​. 运用这个记号…