手撕二叉搜索树

news2024/11/15 22:36:01

目录

一、概念

二、常见操作

2.1 查找操作

2.2 插入操作

2.3 删除操作

三、模型应用

3.1 K模型

3.2 KV模型

3.3 代码完整实现

四、 性能分析


一、概念

二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树

它或者是一棵空树,或者是具有以下性质的二叉树:
1. 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
2. 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
3. 它的左右子树也分别为二叉搜索树
4. 不允许键值冗余

二、常见操作

2.1 查找操作

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

非递归

bool find(const K& key) {
    BSTNode* cur = _root;
	while (cur != nullptr) {
    	if (cur->_key > key) {
        	cur = cur->_left;
	    }
	    else if (cur->_key < key) {
		    cur = cur->_right;
	    }
	    else {//cur->_key == key
		    return true;
	    }
    }
	return false;
}

递归

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

	else if (root->_key > key) return _find(root->_left, key);
	else if (root->_key < key) return _find(root->_right, key);
	else return true;//root->_key == key
}

bool find(const K& key) {
	return _find(_root, key);
}

2.2 插入操作

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

非递归

bool insert(const K& key) {
    //树为空,则直接新增节点,赋值给root指针
	if (_root == nullptr) {
		_root = new BSTNode(key);
		return true;
	}

    //树不空,按二叉搜索树性质查找插入位置
	BSTNode* cur = _root, * parent = nullptr;
	while (cur != nullptr) {
		if (cur->_key > key) {
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key) {
			parent = cur;
			cur = cur->_right;
		}
		else {//cur->_key == key
			return false;//不允许键值冗余,插入失败
		}
	}

    //插入新节点
	cur = new BSTNode(key);
	if (parent->_key > key) parent->_left = cur;
	else parent->_right = cur;

	return true;
}

递归

//root为上一层左指针(右指针)的别名,直接赋值即可
bool _insert(BSTNode*& root, const K& key) {
	if (root == nullptr) {
		root = new BSTNode(key);
		return true;
	}
	else if (root->_key > key) return _insert(root->_left, key);
	else if (root->_key < key) return _insert(root->_right, key);
	else return false;
}

bool insert(const K& key) {
	return _insert(_root, key);
}

2.3 删除操作

删除这个操作具有一定难度,为了使树在完成结点的删除后依然保持二叉树搜索树的性质,必须分情况进行处理。

(1)若删除的是叶结点,则直接删除

(2)若删除的结点只有一株左子树或右子树,则直接将该子树移到被删结点位置

(3)若删除的结点有两株子树,则使用替换法进行删除。在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值与待删除结点的值进行交换,再来处理该结点的删除问题

 非递归

bool erase(const K& key) {
	BSTNode* cur = _root, * parent = nullptr;
	while (cur != nullptr) {
		if (cur->_key > key) {
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key) {
			parent = cur;
			cur = cur->_right;
		}
		else {//cur->_key == key,找到待删除结点,开始删除

            //待删除结点的左子树为空 或 待删除结点左右子树都为空
			if (cur->_left == nullptr) {
				if (cur == _root) {
					_root = cur->_right;
				}
				else {
					if (cur == parent->_left) {
						parent->_left = cur->_right;
					}
					if (cur == parent->_right) {
						parent->_right = cur->_right;
					}
				}
				delete cur;
				cur = nullptr;
			}
            
            //待删除结点的右子树为空
			else if (cur->_right == nullptr) {
				if (cur == _root) {
					_root = cur->_left;
				}
				else {
					if (cur == parent->_left) {
						parent->_left = cur->_left;
					}
					if (cur == parent->_right) {
						parent->_right = cur->_left;
					}
				}
				delete cur;
				cur = nullptr;
			}
            
            //左右都不为nullptr,使用替换法
			else {
                //找到待删除结点右子树的最小结点和其父结点
				BSTNode* replace = cur->_right, * min_parent = cur;
				while (replace->_left != nullptr) {
					min_parent = replace;
					replace = replace->_left;
				}
                //将最小结点的值与待删除结点的值进行交换
				swap(replace->_key, cur->_key);
                
                //最小结点不可能有左子树,接上右子树即可
				if (min_parent->_left == replace) {
					min_parent->_left = replace->_right;
				}
				else {
					min_parent->_right = replace->_right;
				}
				delete replace;
			}
			return true;
		}
	}
	return false;
}

递归

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

	else if (root->_key > key) return _erase(root->_left, key);
	else if (root->_key < key) return _erase(root->_right, key);

	else {//找到待删除结点
		BSTNode* del = root;

        //待删除结点的左子树为空或左右子树都为空
		if (root->_left == nullptr) {
			root = root->_right;
		}

        //待删除结点的右子树为空
		else if (root->_right == nullptr) {
			root = root->_left;
		}
        
        //左右都不为空
		else {
            //找到带删除结点右子树的最小结点
			BSTNode* replace = root->_right;
			while (replace->_left != nullptr) {
				replace = replace->_left;
			}
            //交换值
			swap(replace->_key, root->_key);

			return _erase(root->_right, key);
            //不可写成erase(key),因为重新查找不到
            //此时二叉搜索树的存储性质已被破坏,但待删除结点的右子树依然保持二叉搜索树的性质
		}
		delete del;
		return true;
	}	
}

bool erase(const K& key) {
	return _erase(_root, key);
}

三、模型应用

3.1 K模型

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

3.2 KV模型

每一个关键码key,都有与之对应的值value,即<Key, Value>的键值对
比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对

3.3 代码完整实现

k模型

#define RECURSION
#include<iostream>
#include<algorithm>
using std::swap;
using std::cout;
using std::endl;
namespace KEY
{
	template<class K>
	struct BinarySearchTreeNode
	{
		BinarySearchTreeNode(const K& key = K()) : _left(nullptr), _right(nullptr), _key(key) {}
		BinarySearchTreeNode<K>* _left;
		BinarySearchTreeNode<K>* _right;
		K _key;
	};
	template<class K>
	class BinarySearchTree
	{
		typedef BinarySearchTreeNode<K> BSTNode;
	public:
		BinarySearchTree() = default;//C++11: 强制编译器生成默认构造
		BinarySearchTree(const BinarySearchTree<K>& obj) {
			_root = _copy(obj._root);
		}
		~BinarySearchTree() {
			_destory(_root);
		}
		BinarySearchTree<K>& operator=(BinarySearchTree<K> obj) {
			swap(_root, obj._root);
			return *this;
		}

		bool insert(const K& key) {
#ifdef RECURSION
			return _insert(_root, key);
#else
			if (_root == nullptr) {
				_root = new BSTNode(key);
				return true;
			}
			BSTNode* cur = _root, * parent = nullptr;
			while (cur != nullptr) {
				if (cur->_key > key) {
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key) {
					parent = cur;
					cur = cur->_right;
				}
				else {//cur->_key == key
					return false;
				}
			}
			cur = new BSTNode(key);
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = cur;
			return true;
#endif
		}

		bool erase(const K& key) {
#ifdef RECURSION
			return _erase(_root, key);
#else
			BSTNode* cur = _root, * parent = nullptr;
			while (cur != nullptr) {
				if (cur->_key > key) {
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key) {
					parent = cur;
					cur = cur->_right;
				}
				else {//cur->_key == key
					if (cur->_left == nullptr) {
						if (cur == _root) {
							_root = cur->_right;
						}
						else {
							if (cur == parent->_left) {
								parent->_left = cur->_right;
							}
							if (cur == parent->_right) {
								parent->_right = cur->_right;
							}
						}
						delete cur;
						cur = nullptr;
					}
					else if (cur->_right == nullptr) {
						if (cur == _root) {
							_root = cur->_left;
						}
						else {
							if (cur == parent->_left) {
								parent->_left = cur->_left;
							}
							if (cur == parent->_right) {
								parent->_right = cur->_left;
							}
						}
						delete cur;
						cur = nullptr;
					}
					else {
						BSTNode* replace = cur->_right, * min_parent = cur;
						while (replace->_left != nullptr) {
							min_parent = replace;
							replace = replace->_left;
						}
						swap(replace->_key, cur->_key);
						if (min_parent->_left == replace) {
							min_parent->_left = replace->_right;
						}
						else {
							min_parent->_right = replace->_right;
						}
						delete replace;
					}
					return true;
				}
			}
			return false;
#endif 
		}

		bool find(const K& key) {
#ifdef RECURSION
			return _find(_root, key);
#else
			BSTNode* cur = _root;
			while (cur != nullptr) {
				if (cur->_key > key) {
					cur = cur->_left;
				}
				else if (cur->_key < key) {
					cur = cur->_right;
				}
				else {//cur->_key == key
					return true;
				}
			}
			return false;
#endif
		}

		void inorder() {
			_inorder(_root);
		}

	private:
		BSTNode* _copy(BSTNode* root) {
			if (root == nullptr) return nullptr;
			BSTNode* copy_root = new BSTNode(root->_key);
			copy_root->_left = _copy(root->_left);
			copy_root->_right = _copy(root->_right);
			return copy_root;
		}

		bool _insert(BSTNode*& root, const K& key) {//root为上一层左指针(右指针)的别名,直接赋值即可
			if (root == nullptr) {
				root = new BSTNode(key);
				return true;
			}
			else if (root->_key > key) return _insert(root->_left, key);
			else if (root->_key < key) return _insert(root->_right, key);
			else return false;
		}

		bool _erase(BSTNode*& root, const K& key) {
			if (root == nullptr) return false;
			else if (root->_key > key) return _erase(root->_left, key);
			else if (root->_key < key) return _erase(root->_right, key);
			else {
				BSTNode* del = root;
				if (root->_left == nullptr) {
					root = root->_right;
				}
				else if (root->_right == nullptr) {
					root = root->_left;
				}
				else {//左右都不为空
					BSTNode* replace = root->_right;
					while (replace->_left != nullptr) {
						replace = replace->_left;
					}
					swap(replace->_key, root->_key);
					return _erase(root->_right, key);
				}
				delete del;
				return true;
			}
		}

		bool _find(BSTNode* root, const K& key) {
			if (root == nullptr) return false;
			else if (root->_key > key) return _find(root->_left, key);
			else if (root->_key < key) return _find(root->_right, key);
			else return true;//root->_key == key
		}

		void _inorder(BSTNode* root) {
			if (root == nullptr) {
				return;
			}
			_inorder(root->_left);
			cout << root->_key << " ";
			_inorder(root->_right);
		}

		void _destory(BSTNode*& root) {
			if (root == nullptr) {
				return;
			}
			_destory(root->_left);
			_destory(root->_right);
			delete root;
			root = nullptr;
		}
	private:
		BSTNode* _root = nullptr;
	};
}

KV模型

namespace KEY_VALUE
{
	template<class K,class V>
	struct BinarySearchTreeNode
	{
		BinarySearchTreeNode(const K& key = K(), const V& value = V()) : _left(nullptr), _right(nullptr), _key(key), _value(value) {}
		BinarySearchTreeNode<K,V>* _left;
		BinarySearchTreeNode<K,V>* _right;
		K _key;
		V _value;
	};
	template<class K,class V>
	class BinarySearchTree
	{
		typedef BinarySearchTreeNode<K, V> BSTNode;
	public:
		bool insert(const K& key,const V& value) {
			if (_root == nullptr) {
				_root = new BSTNode(key,value);
				return true;
			}
			BSTNode* cur = _root, * parent = nullptr;
			while (cur != nullptr) {
				if (cur->_key > key) {
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key) {
					parent = cur;
					cur = cur->_right;
				}
				else {//cur->_key == key
					return false;//不允许键值冗余,插入失败
				}
			}
			cur = new BSTNode(key,value);
			if (parent->_key > key) parent->_left = cur;
			else parent->_right = cur;
			return true;
		}

		BSTNode* find(const K& key) {
			BSTNode* cur = _root;
			while (cur != nullptr) {
				if (cur->_key > key) {
					cur = cur->_left;
				}
				else if (cur->_key < key) {
					cur = cur->_right;
				}
				else {//cur->_key == key
					return cur;
				}
			}
			return nullptr;
		}

		void inorder() {
			_inorder(_root);
		}
		private:
		void _inorder(BSTNode* root) {
			if (root == nullptr) {
				return;
			}
			_inorder(root->_left);
			cout << root->_key << ":" << root->_value << " ";
			_inorder(root->_right);
		}
	private:
		BSTNode* _root = nullptr;
	};
}

四、 性能分析

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

对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: log_2 N
最差情况下,二叉搜索树退化为单支树(或者类似单支),若插入顺序有序即会出现单支的情况

问题:
若退化成单支树,二叉搜索树的性能就失去了。

那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?

使用AVL树和红黑树

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

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

相关文章

Spring整合Mybatis和Junit小案例(9)

Spring整合Mybatis和Junit环境准备步骤1&#xff1a;准备数据库步骤2&#xff1a;创建项目导入jar包步骤3&#xff1a;根据数据库的表创建模型类步骤4&#xff1a;创建Dao接口步骤5&#xff1a;创建Service接口和实现类步骤6&#xff1a;添加jdbc.properties文件步骤7&#xff…

5种常用格式的数据输出,手把手教你用Pandas实现

导读:任何原始格式的数据载入DataFrame后,都可以使用类似DataFrame.to_csv()的方法输出到相应格式的文件或者目标系统里。本文将介绍一些常用的数据输出目标格式。 01 CSV DataFrame.to_csv方法可以将DataFrame导出为CSV格式的文件,需要传入一个CSV文件名。 df.to_csv(done.…

在 SPRING Boot JPA 中调用带有本机查询中的参数的存储过程

配置pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.…

惊了!10万字的Spark全文!

Hello&#xff0c;大家好&#xff0c;这里是857技术社区&#xff0c;我是社区创始人之一&#xff0c;以后会持续给大家更新大数据各组件的合集内容&#xff0c;路过给个关注吧!!! 今天给大家分享一篇小白易读懂的 Spark万字概念长文&#xff0c;本篇文章追求的是力求精简、通俗…

Linux(基于Centos7)(一)

文章目录一、任务介绍二、基本操作命令三、目录操作命令四、文件操作命令五、查看系统信息六、其他常用命令一、任务介绍 Linux服务器配置与管理&#xff08;基于Centos7.2&#xff09;任务目标&#xff08;一&#xff09; 实施该工单的任务目标如下&#xff1a; 知识目标 1、…

RNA剪接增强免疫检查点抑制疗效

什么是 RNA 剪接&#xff1f;真核生物基因包含一系列外显子和内含子&#xff0c;内含子必须在转录过程中被移除以便成熟的 mRNA 被翻译成蛋白质&#xff0c;RNA 剪接则是这一过程中至关重要的一步。RNA 剪接包含两类剪接事件。组成型剪接 (constitutive splicing): RNA 剪接的一…

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-职业院校组 | 精品题解

&#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f5a5;️ Nodejs专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ TS知识总结&#xff1a;十万字TS知识点总结 &#x1f449; 你的一键三连是我更新的最大动力❤️&#xff0…

企业级Java EE架构设计精深实践

内容简介 本书全面、深入介绍了企业级Java EE设计的相关内容&#xff0c;内容涵盖了Java EE架构设计的常见问题。 本书每一章讲解一个Java EE领域的具体问题&#xff0c;采用问题背景、需求分析、解决思路、架构设计、实践示例和章节总结的顺序组织内容&#xff0c;旨在通过分…

生成树(STP)

1.详细说明STP的工作原理 在二层交换网络中&#xff0c;逻辑的阻塞部分的接口&#xff0c;实现从跟交换机到所有节点唯一的路径称为最佳路径&#xff0c;生成一个没有环路的拓扑。当最佳路径出现故障时&#xff0c;个别被阻塞的接口将打开&#xff0c;形成备份链路。 2. STP的…

Redis的发布和订阅

Redis的发布和订阅 什么是发布和订阅 redis发布订阅&#xff08;pub/sub&#xff09;是一种消息通信模式&#xff1a;发布者&#xff08;pub&#xff09;发布消息&#xff0c;订阅者&#xff08;sub&#xff09;接收消息。 redis客户端可以订阅任意数量的频道。 redis的发布…

vue3【计算属性与监听-详】

一、计算属性--简写形式 需求&#xff1a;通过计算属性&#xff0c;计算一个人的全名。 <template><h1>基本信息</h1>姓&#xff1a;<input type"text" v-model"personInfo.firstName"><hr>名&#xff1a;<input type&…

综合实验高级网络—— 配置三层 热备等网络技术

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。个人爱好: 编程&#xff0c;打篮球&#xff0c;计算机知识个人名言&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石…

结合邻域连接法的蚁群优化(NACO)求解TSP问题(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

傅里叶级数与傅里叶变换

一、Games101 中出现的傅里叶变换(FT)的简单推导 到底什么是傅里叶变换&#xff1a;它的物理意义是什么&#xff0c;公式又从何而来&#xff1f; 以下的内容出现在 Games101 中的第八章&#xff1a;光栅化&#xff08;深度测试与抗锯齿&#xff09; 中&#xff0c;课程中这一部…

OpenAI Whisper论文笔记

OpenAI Whisper论文笔记 OpenAI 收集了 68 万小时的有标签的语音数据&#xff0c;通过多任务、多语言的方式训练了一个 seq2seq &#xff08;语音到文本&#xff09;的 Transformer 模型&#xff0c;自动语音识别&#xff08;ASR&#xff09;能力达到商用水准。本文为李沐老师…

Spring源码-doCreateBean

先看段代码&#xff1a; Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)throws BeanCreationException {if (logger.isDebugEnabled()) {logger.debug("Creating instance of bean " beanName "&qu…

Yolov5(v5.0) + pyqt5界面设计

1.下载安装pyqt5工具包以及配置ui界面开发环境 pip install PyQt5 pip install PyQt5-tools 2.点击File->Settings->External Tools进行工具添加&#xff0c;依次进行Qt Designer、PyUIC环境配置. 2.1 添加QtDesigner Qt Designer 是通过拖拽的方式放置控件&#xff0c…

GUI编程--PyQt5--QWidget2

文章目录事件事件传递父子关系扩展Z轴的层级关系事件 自定义控件类&#xff08;QWidget&#xff09;&#xff0c;然后重写对应的事件方法即可。 控件显示时触发&#xff0c;showEvent(QShowEvent) 控件关闭时触发&#xff0c;closeEvent(QCloseEvent) 事件传递 案例&#xff1…

MYSQL -- Binlog数据还原

对表误删或执行缺少条件的修改 SQL 导致修改了表内其他数据时&#xff0c;我们需要想办法将数据恢复回来。 先创建两个测试表 table_1 CREATE TABLE table_1 (id int(0) NOT NULL AUTO_INCREMENT,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL D…

SpringBoot自定义注解+AOP+redis实现防接口幂等性重复提交,从概念到实战

一、前言 在面试中&#xff0c;经常会有一道经典面试题&#xff0c;那就是&#xff1a;怎么防止接口重复提交&#xff1f; 小编也是背过的&#xff0c;好几种方式&#xff0c;但是一直没有实战过&#xff0c;做多了管理系统&#xff0c;发现这个事情真的没有过多的重视。 最近…