C++泛型实现搜索二叉树

news2024/11/18 14:56:28

文章目录

    • 二叉搜索树
      • 查找
      • 插入
      • 删除
      • 实现
      • 应用
      • 性能分析

二叉搜索树

二叉搜索树(BST,Binary Search Tree)又称为二叉排序树,空树也算

二叉搜索树有如下性质

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

例如

屏幕截图 2024-03-08 204448.png

当然如果左大右小也可以

二叉搜索树的一个性质是中序遍历有序

查找

从根节点开始查找比较,比根大向右查找,比根小向左查找

最多查找高度次,如果没找到就代表值不存在

插入

如果为空,新增节点

如果不为空,按照性质插入节点

删除

首先需要确定值是否在二叉树中

要删除就右四种情况

  1. 无子节点——直接删除即可,可以合并到只有一个节点的情况
  2. 只有左节点——删除,令该节点的父节点指向左节点
  3. 只有右节点——删除,令该节点的父节点指向右节点
  4. 有两个子节点——在左子树寻找关键之最大的节点或右子树的最小节点,以最小节点为例,找到最小节点后与删除节点替换,再处理替换后的节点删除问题

实现

#pragma once
#include<iostream>

using namespace std;

template<class K> 
struct BSTreeNode // 二叉树节点,K表示关键字
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;

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

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// C++11
	BSTree() = default; // 强制生成默认构造

	~BSTree()
	{
		Destroy(_root);
	}

	BSTree(const BSTreeNode<K>& t)
	{
		_root = Copy(t._root);
	}

	BSTree<K>& operator=(BSTree<k> t)
	{
		swap(_root, t._root);
		return *this;
	}

	bool Insert(const K& key) // 建树,插入
	{
		if (_root == nullptr) // 空树
		{
			_root = new Node(key);
			return tree;
		}

		Node* parent = nullptr;
		Node* cur = _root;

		while (cur) // 找位置
		{
			parent = cur;
			if (cur->_key < key)
				cur = cur->_left; // 插入值比当前值小,进左树
			else if (cur->_key > key)
				cur = cur->_right; // 插入值比当前值大,进右树
			else
				return false; // 不允许出现重复值
		}

		cur = new Node(key);
		if (parent->_key < key) // 连接父节点
			parent->_right = cur;
		else
			parent->_left = cur;
	}

	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 Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else // 找到了,准备删除
			{
				if (cur->_left == nullptr) // 左子树为空
				{
					// 当删除节点为根节点时,直接让根节点变为右子树即可
					if (cur == _root)
					{
						_root = cur->_right;
					}
					// 当删除节点不是根节点时,需要连接父节点和右子树
					else
					{
						// 判断当前节点是父节点的左孩子还是右孩子,确保正确连接
						if (cur == parent->_left)
						// 这里为什么不用防止parent是空指针呢
						// 因为只有根节点没有父节点,而前面一个判断已经把根节点单独处理过了
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;
					}
					delete cur;
				}
				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;
					}
					delete cur;
				}
				else // 左右都不为空
				{
					// 找到右子树的最小节点,替换后删除
					parent = cur; // 因为后面需要替换,防止出现解引用空指针
					Node* SubLeft = cur->_right; 
					// 表示右子树最小值,他一定在右子树的最高的最左边的那个节点上
					while (SubLeft->_left) // 找到该节点
					{
						parent = SubLeft;
						SubLeft = SubLeft->_left;
					}

					swap(cur->_key, SubLeft->_key); // 交换节点值

					// 最左节点一定是左子树为空,因此只需要父节点连接最左节点的右子树
					if (SubLeft == parent->_left) // 判断该节点是父节点的左节点还是右节点,再连接
						parent->_left = SubLeft->_right;
					else
						parent->_right = SubLeft->_right;
					delete SubLeft;
				}
				return true;
			}
			return false;
		}
	}

	void InOrder() // 中序遍历打印
	{
		// 中序遍历需要根节点,又不希望类外得到根节点
		// 这里可以只实现一个接口,内容是private即可
		// 后面的同理
		_InOrder(_root);
		cout << endl;
	}

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

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

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
private:
	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;
	}

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

		Destroy(root->_left);
		Destroy(root->_right);
		delete root;
		root = nullptr;
	}

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

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

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

	bool _InsertR(Node*& root, const K& key)
	{
		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
			return false;
	}

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

		if (root->_key < key)
			return _EraseR(root->_right, key);
		else if (root->_key > key)
			return _EraseR(root->_left, key);
		else
		{
			if (root->_left == nullptr)
			{
				Node* del = root;
				root = root->_right; // root也是父节点左右子树的别名,因此直接赋值
				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);
			}
		}
	}

	Node* _root = nullptr;
};

应用

二叉搜索树一般有两个应用

第一类是K模型,结构中只需要存储Key即可,关键之就是所需要的值,一般用于检测某个值是否存在

第二类是KV模型,结构中是<Key,Value>键值对,类似于字典

性能分析

插入和删除都必须先查找

插入的次序不同,会影响到二叉树的结构

最优情况下,二叉树为完全二叉树,其平均比较次数为 log ⁡ 2 N \log_2N log2N

最差情况下,二叉树为单支树,其平均比较次数为 N 2 \frac{N}{2} 2N

因此当二叉树为单支树,我们应当如何改进,使其性能都达到最优,就需要引入AVL树和红黑树,这些我们在后面也会陆续讲解和实现

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

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

相关文章

USB2.0设备检测过程信号分析

1.简介 USB设备接入的Hub端口负责检测USB2.0设备是否存在和确定USB2.0设备的速度。检测设备是否存在和确定设备速度涉及一系列的信号交互&#xff0c;下面将分析该过程。 2.硬件 USB低速设备和全速/高速设备的连接器在硬件结构上有所不同&#xff0c;而主机或者Hub接收端连接…

NTFS安全权限

NTFS是新技术文件系统&#xff08;New Technology File System&#xff09;的缩写&#xff0c;是一种用于Windows操作系统的文件系统。NTFS提供了高级的功能和性能&#xff0c;包括文件和目录的权限控制、加密、压缩以及日志等。它被广泛应用于Windows NT、Windows 2000、Windo…

基于SSM的大王门店管理系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 SSM 3 1.1.1 Spring 3 1.1.2 Spring MVC 3 1.1.3 MyBatis 4 1.2 Shiro 4 1.3 前端技术 4 1.3.1 Bootstrap 4 1.3.2 jQuery 4 1.3.3 Ajax 5 1.3.4 Layui 5 1.3.5 Thymeleaf 5 1.4 本章小结 6 2 系统分析 7 2.1 功能需求分析…

云计算 3月8号 (wordpress的搭建)

项目wordpress 实验目的&#xff1a; 熟悉yum和编译安装操作 锻炼关联性思维&#xff0c;便于以后做项目 nginx 编译安装 1、安装源码包 [rootlinux-server ~]# yum -y install gcc make zlib-devel pcre pcre-devel openssl-devel [rootlinux-server ~]# wget http://nginx.…

数据库 04-01 数据库的设计

01.设计的第一步&#xff0c;记录用户的信息 02.第二步概念转换成数据库概念 概念设计是什么&#xff1a; 着重点&#xff1a; 03.第三步完成增删改查的功能设计 04.最后的在数据库上实现

[HackMyVM]靶场 Zeug

kali:192.168.56.104 主机发现 arp-scan -l # arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:d2:e0:49, IPv4: 192.168.56.104 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.56.1 0a:00:27:00:00:05 (Un…

Vue事件处理:.passive修饰符与应用场景

.passive修饰符 passive这个修饰符会执行默认方法。你们可能会问&#xff0c;明明默认执行为什么会设置这样一个修饰符。这就要说一下这个修饰符的本意了。 浏览器只有等内核线程执行到事件监听器对应的JavaScript代码时&#xff0c;才能知道内部是否会调用preventDefa…

数据结构——lesson7二叉树 堆的介绍与实现

前言&#x1f49e;&#x1f49e; 啦啦啦~这里是土土数据结构学习笔记&#x1f973;&#x1f973; &#x1f4a5;个人主页&#xff1a;大耳朵土土垚的博客 &#x1f4a5; 所属专栏&#xff1a;数据结构学习笔记 &#x1f4a5;对于数据结构顺序表链表有疑问的都可以在上面数据结…

JavaWeb笔记 --- 一JDBC

一、JDBC JDBC就是Java操作关系型数据库的一种API DriverManager 注册驱动可以不写 Class.forName("com.mysql.jdbc.Driver"); Connection Statement ResultSet PrepareStatement 密码输入一个SQL脚本&#xff0c;直接登录 预编译开启在url中 数据库连接池

程序如何知道mqtt设备是否在线

在做物联网设备的时候经常会碰到设备的在线与掉线 问题&#xff1a;emqx如何来实现这个在线与掉线 实现&#xff1a;添加一个规则&#xff0c;程序监控这个规则 1、SELECT * FROM "$events/client_connected", "$events/client_disconnected" 2、添加一…

10 事务控制

文章目录 事务控制事务概述事务操作事务四大特性事务隔离级别 事务控制 事务概述 MySQL 事务主要用于处理操作量大&#xff0c;复杂度高的数据。比如说&#xff0c;在人员管理系统中&#xff0c;你删除一个人员&#xff0c;既需要删除人员的基本资料&#xff0c;也要删除和该…

leetcode 热题 100_除自身以外数组的乘积

题解一&#xff1a; 前缀 / 后缀数组&#xff1a;某元素除自身以外的乘积&#xff0c;也就是其全部前缀元素乘积 * 全部后缀元素乘积&#xff0c;因此我们可以构造前缀数组和后缀数组&#xff0c;分别存储前i个元素的成绩和后i个元素的乘积&#xff0c;再将i-1前缀乘积 * i1后缀…

C及C++每日练习(3)

选择题&#xff1a; 1.以下程序的输出结果是&#xff08;&#xff09; #include <stdio.h> main() { char a[10] {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}, *p; int i; i 8; p a i; printf("%s\n", p - 3); } A.6 B. 6789 C. 6 D.789 对于本题&#xff0…

亚信安慧AntDB:企业数据管理的明日之星

在信息科技飞速发展的时代&#xff0c;亚信科技AntDB团队提出了一项颠覆性的“超融合”理念&#xff0c;旨在满足企业日益增长的复杂混合负载和多样化数据类型的业务需求。这一创新性框架的核心思想在于融合多引擎和多能力&#xff0c;充分发挥分布式数据库引擎的架构优势&…

unicloud 集合 Collection 详解及其使用示例

Collection Collection是unicloud数据的指定表集合 获取集合Collection示例如下 const db uniCloud.database(); // 获取 user 集合的引用 const collection db.collection(user);集合 Collection 通过 db.collection(name) 可以获取指定集合的引用&#xff0c;在集合上可…

网络工程师笔记9

动态路由 RIP路由协议 配置简单 易于维护 适用于小型网络 周期性是30s发一次

HTML—常用标签

常用标签&#xff1a; 标题标签&#xff1a;<h1></h1>......<h6></h6>段落标签&#xff1a;<p></p>换行标签&#xff1a;<br/>列表&#xff1a;无序列表<ul><li></li></ul> 有序列表<ol>&…

《解密云计算:企业之选》

前言 在当今数字化时代&#xff0c;企业面临着巨大的数据处理压力和信息化需求&#xff0c;传统的IT架构已经无法满足日益增长的业务需求。在这样的背景下&#xff0c;越来越多的企业开始转向云计算&#xff0c;以实现灵活、高效和可扩展的IT资源管理和利用。 云计算 云计算是…

css使用

文章目录 一、什么是CSS二、CSS导入方式三、CSS选择器四、CSS属性 一、什么是CSS <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>div{color: red;}</style> &…

清华大学1748页CTF竞赛入门指南,完整版开放下载!

CTF是一种针对信息安全领域的经济性挑战&#xff0c;旨在通过解决一系列的难题来寻找隐藏的“flag”。CTF比赛战队一般是以高校、科研单位、企业、信息安全从业者或社会团体组成。对于网安爱好者及从业者来说&#xff0c;拥有“CTF参赛经验”也是求职中的加分项。 前几天分享的…