C++——二叉树排序树

news2024/11/16 15:50:46

文章目录

    • 1 二叉搜索树概念
    • 2 二叉搜索树操作与模拟实现
      • 2.1 二叉搜索树的查找
      • 非递归版本
      • 递归版本
      • 2.2 二叉搜索树的插入
      • 非递归版本
      • 递归版本
      • 2.3 二叉搜索树的删除
      • 非递归版本
      • 递归版本
    • 3 二叉搜索树的应用(K模型、KV模型)
    • 4 二叉搜索树的性能分析

1 二叉搜索树概念

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

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树
  • 一般情况下二叉搜索树的数据是不能重复的
    在这里插入图片描述

2 二叉搜索树操作与模拟实现

完整代码K模型和KV模型代码链接
结点定义:

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

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

typedef BSTreeNode<K> Node;

2.1 二叉搜索树的查找

在这里插入图片描述

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

代码实现:

非递归版本

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

2.2 二叉搜索树的插入

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

代码实现:

非递归版本

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	Node* parent = nullptr;//记录父节点
	Node* cur = _root;//_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此时为nullptr
	//父节点parent也被记录
	cur = new Node(key);
	
	if (parent->_key < key)//寻找该正确的插入位置
	{
		parent->_right = cur;
	}
	else
	{
		parent->_left = cur;
	}
	return true;
}

递归版本

可以考虑再增加一个parent参数。
但下面的方法更加巧妙
下面代码传root的引用两个用处:
1、如果传进来的就是一颗空树,直接给root赋值就可以了,比较容易理解.
2、因为传的是引用,所以root也是上一步递归传入的root->right或者root->_left的别名,非常巧妙的是root = new Node(key);就等价于root->right=new Node(key);或者root->_left=new Node(key);,刚好把结点就赋值到正确的位置,省去了上面非递归方法的还需要记录父节点的步骤。

bool _InsertR(Node*& root, const K& key)//注意细节传的是root的引用
{	
	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//插入的值和当前结点的值相等,不需要插入,返回false
		return false;
}

下面是插入9的代码调试,可以发现确实root和上一步root->left的地址是一样的,可见引用在C++中的作用是多么大。

在这里插入图片描述

2.3 二叉搜索树的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点(例如1、4、7、13)
b. 要删除的结点只有左孩子结点(例如14)
c. 要删除的结点只有右孩子结点(例如10)
d. 要删除的结点有左、右孩子结点(例如3、6、8)
看起来删除节点有4种情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:

  • 情况b:删除该结点,并且使该删除节点的双亲结点指向该删除节点的左孩子结点——直接删除(托孤)
  • 情况c:删除该结点,并且使该删除节点的双亲结点指向该删除结点的右孩子结点——直接删除(托孤)
  • 情况d:
    找被删除结点 左子树的最大 或者 右子树的最小 用它的值填补到被删除节点中(这样才能保证二叉排序树的结构不被破坏),再来删除这个 左子树最大 或者 右子树最小结点——替换法删除。

针对情况d,下面图中,想删除8用左子树的最大或者右子树的最小
那么由于二叉排序树特殊的结构其实
左子树的最大 也就是 左子树的最右结点——从上往下遍历,第一个没有右孩子的结点——7
右子树的最小 也就是 右子树的最左结点——从上往下遍历,第一个没有左孩子的结点——10

在这里插入图片描述

代码实现:
提前声明:下面非递归和递归版本都用的是右子树的最小结点来进行替换删除的。
具体细节详解在下面代码注释中:一边看图一边看代码注释效果更佳!

非递归版本

bool Erase(const K& key)
{
	Node* parent = nullptr;//记录父节点
	Node* cur = _root;//_root成员变量
	while (cur)
	{//能找到开始删除
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else//被删除目标已经找到,就存在cur里
		{
			if (cur->_left == nullptr)//被删除结点的左为空——只有右孩子
			{
				//if (parent == 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 (parent == 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; //细节赋值cur
				Node* minRight = cur->_right;
				while (minRight->_left)
				{
					parent = minRight;
					minRight = minRight->_left;
				}
				
				//运行到这里已经找到了右子树最小结点
				cur->_key = minRight->_key;//直接覆盖被删除结点的值,或者让右子树和被删除结点交换都可以
				
				//minRight是右子树的最左结点——一定没有左孩子
				//而且要特别注意:删除3和删除8结点是不一样的,一定要考虑全面
				if (minRight == parent->_left)
				//删除8结点的情况,对于10结点的处理
				{
					parent->_left = minRight->_right;
				}
				else//删除3结点的情况,对于4结点的处理
				{
					parent->_right = minRight->_right;
				}
				delete minRight;
			}

			return true;
		}
	}
	//找不到需要被删除的节点返回false
	return false;
}

递归版本

bool _EraseR(Node*& root, const K& key)
{
	if (root == nullptr)//没有找到 或者 root一开始传入的就是一个空树
	{
		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;//记录当前结点,方便后面delete
		
		//被删除结点只有左孩子,例如14
		if (root->_right == nullptr)
		{
			root = root->_left;//被赋值的root也是上一层递归root->_right的别名
		}
		//被删除结点只有右孩子,例如:10
		else if (root->_left == nullptr)
		{
			root = root->_right;//被赋值的root也是上一层递归root->_left的别名
		}
		else//被删除结点有两个孩子
		{
			Node* minRight = root->_right;
			//找右子树的最左——最小
			while (minRight->_left)
			{
				minRight = minRight->_left;
			}
			//让被删除结点跟最小值交换
			swap(root->_key, minRight->_key);
			// 交换完右子树还是一颗二叉排序树
			// 转换成在子树中去删除节点
			return _EraseR(root->_right, key);
		}

		delete del;

		return true;
	}
}

完整代码K模型和KV模型代码链接

3 二叉搜索树的应用(K模型、KV模型)

  1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
  • 以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
  1. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:
  • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
  • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

4 二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N {N} N

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

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

相关文章

Go语言设计与实现 -- 反射

Go的反射有哪些应用&#xff1f; IDE中代码的自动补全对象序列化fmt函数的相关实现ORM框架 什么情况下需要使用反射&#xff1f; 不能明确函数调用哪个接口&#xff0c;需要根据传入的参数在运行时决定。不能明确传入函数的参数类型&#xff0c;需要在运行时处理任意对象。 …

1.TCP、UDP区别、TCP/IP七层、四层模型、应用层协议(计网)

文章目录1.OSI 七层模型是什么&#xff1f;每一层的作用是什么&#xff1f;2.TCP/IP 四层模型是什么&#xff1f;每一层的作用是什么&#xff1f;应用层&#xff08;Application layer&#xff09;传输层&#xff08;Transport layer&#xff09;网络层&#xff08;Network lay…

百度工程师带你探秘C++内存管理

一、概述 ptmalloc是开源GNU C Library(glibc)默认的内存管理器&#xff0c;当前大部分Linux服务端程序使用的是ptmalloc提供的malloc/free系列函数&#xff0c;而它在性能上远差于Meta的jemalloc和Google的tcmalloc。服务端程序调用ptmalloc提供的malloc/free函数申请和释放内…

Datawhale组队学习:大数据 D2——分布式文件系统(HDFS)

妙趣横生大数据 Day2三、Hadoop 分布式文件系统(HDFS)1. 分布式文件系统2. HDFS 简介3. HDFS 体系结构4. HDFS存储原理数据冗余存储数据存储策略数据错误与恢复5. HDFS数据读写过程读写过程HDFS故障类型和其检测方法HDFS编程实验1. 本地和集群文件间操作2. 基本文件操作3. Hado…

Java基本语法【未完待续】

目录 一、注释方式 1、单行注释 // 2、多行注释 /*...*/ 3、文档注释 /**....*/ 二、标识符和关键字 三、数据类型 拓展及面试题讲解 1、整数拓展 进制 二进制0b 八进制0 十六进制0x 2、字符拓展 编码Unicode表 2字节 0~65536 3、字符串拓展 4、布尔值拓展 四、类型…

CleanMyMac X软件下载及详细功能介绍

mac平台的知名系统清理应用CleanMyMac在经历了一段时间的测试后&#xff0c;全新设计的X正式上线。与CleanMyMac3相比&#xff0c;新版本的UI设计焕然一新&#xff0c;采用了完全不同的风格。使用Windows电脑时&#xff0c;很多人会下载各类优化软件&#xff0c;而在Mac平台中&…

jsp高校教职工管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 jsp 高校教职工管理系统 是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助mvc模式 serlvetdaobean方式开发&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式 开发。开发环境为TOMCAT7.0,Myeclipse8.5开发&#…

tomcat安装和配置

目录 1.下载tomcat 2.解压tomcat压缩包 3.配置端口号 4.启动 命令行窗口日志乱码的解决 5.验证 tomcat 如果已经安装配置过jdk&#xff0c;则向下执行&#xff0c;若无&#xff0c;请先安装jdk。 1.下载tomcat 路径&#xff1a; Apache Tomcat - Apache Tomcat 9 Sof…

Windows 系统从零配置 Python 环境,安装CUDA、CUDNN、PyTorch 详细教程

文章目录1 配置 python 环境1.1 安装 Anaconda1.2 检查环境安装成功1.3 创建虚拟环境1.4 进入/退出 刚刚创建的环境1.5 其它操作1.5.1 查看电脑上所有已创建的环境1.5.2 删除已创建的环境2 安装 CUDA 和 CUDNN2.1 查看自己电脑支持的 CUDA 版本2.2 安装 CUDA2.3 安装 CUDNN2.4 …

LabVIEW中CPU和内存使用情况在NI分布式系统管理器中不可见

LabVIEW中CPU和内存使用情况在NI分布式系统管理器中不可见想使用NI分布式系统管理器监测网络连接实时控制器的CPU和内存使用情况。从左侧窗口的树中选择了感兴趣的实时目标&#xff0c;然后通过选择视图自动视图来确保启用自动查看。希望看到CPU/内存选项卡&#xff0c;但它有显…

算法导论【在线算法】—The Ski-Rental Problem、The Lost Cow Problem、The Secretary Problem

算法导论【在线算法】The Ski-Rental Problem问题描述在线算法证明The Lost Cow Problem问题描述在线算法类似问题—寻宝藏The Secretary Problem问题描述在线算法The Best Possible kThe Ski-Rental Problem 问题描述 假设你正在上滑雪课。每节课结束后&#xff0c;你决定&a…

【Element】el-table 表格

目录 ElementUI 表格分页&#xff08;每页20条&#xff09; 表格分页&#xff08;全部数据&#xff09; 表格排序&#xff08;全部数据&#xff09; 表格排序&#xff08;默认&#xff09; 两个el-table冲突 加载数据前显示“ 暂无数据 ” 表格项为路由 表头样式 树形…

Homebrew 安装遇到的问题

Homebrew 安装遇到的问题 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录Homebrew 安装遇到的问题前言一、安装二、遇到的问题1.提示 zsh: command not found: brew三、解决问题前言 使用 Homebrew 能够 安装 Apple&#xff08;或您的 Linux 系统&#…

React 合成事件理解

1 事件三个阶段 捕获、目标、处理 &#xff08;具体百度&#xff0c;后面有空补全&#xff09;2import React from "react";class Test extends React.Component {parentRef;childRef;constructor(props) {super(props);this.parentRef React.createRef();this.chil…

cmd 窗口、记事本打开后一片空白且几秒钟后闪退的问题解决方案汇总

前言 前段时间&#xff0c;电脑忽然出现了问题&#xff0c;首先是通过 微软应用商店 Microsoft Store 下载安装的 Snipaste 截图软件崩溃&#xff0c;不过将其卸载后&#xff0c;通过电脑管家下载后又可以正常使用了。 之后就是突然发现&#xff0c;记事本文本文档不能使用了…

分享112个HTML娱乐休闲模板,总有一款适合您

分享112个HTML娱乐休闲模板&#xff0c;总有一款适合您 112个HTML娱乐休闲模板下载链接&#xff1a;https://pan.baidu.com/s/15uBy1SVSckPPMM55fiudeQ?pwdkqfz 提取码&#xff1a;kqfz Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 Bootstrap视频网站模板 …

Terraform基础入门 (Infrastructure as Code)

文章目录前言介绍Terraform 术语Terraform 如何工作关于provider安装开启本地缓存demo1(dockernginx)demo2(dockerzookeeperkafka)参考资料前言 像写代码一样管理基础设施。 Terraform 使用较为高级的配置文件语法来描述基础设施&#xff0c;这个特性让你对配置文件进行版本化…

Ubuntu升级cmake

目录 1、下载cmake安装包 2、开始安装 3、查看cmake版本 参考链接&#xff1a; https://blog.csdn.net/qq_27350133/article/details/121994229 1、下载cmake安装包 cmake安装包下载&#xff1a;download | cmake 我们根据自身需求下载所需版本的cmake安装包&#xff0c;这…

万字干货 | 荔枝魔方基于云原生的架构设计与实践

近年来&#xff0c;荔枝集团在国内和海外的业务迅速发展&#xff0c;业务数据规模也是成几何式地增长&#xff0c;海量数据的计算分析场景、业务智能算法应用需求随之而生&#xff0c;为了快速地满足业务发展的需要&#xff0c;我们面临着诸多的技术挑战。技术挑战工程问题资源…

计算机如何思考与图灵完备

图灵完备是针对一套数据操作规则而言的概念,数据操作规则可以是一门编程语言,也可以是计算机实现里面的指令集,比如C/C++是图图灵完备的,通用CPU也是图灵完备的,但是GPU却不一定是图灵完备的。说白了图灵完备定义了一套规则,当这套规则可以实现图灵迹模型里的全部功能时,…