二叉搜索树BST ——(C++)

news2024/9/24 11:23:02

        本篇将会讲解有关二叉树的搜索原理,以及关于二叉搜索树的建立,以及二叉树搜索树的插入、删除和查找等基本操作。最后我们还会对二叉搜索树进行功能扩展,介绍有关搜索二叉树的 K 模型和 KV 模型。目录如下:

目录

1. 搜索二叉树

二叉搜索树概念

二叉树类框架

搜索二叉树的插入

搜索二叉树的查找

搜索二叉树的遍历

搜索二叉树的删除

搜索二叉树所有代码

测试

 2. 搜索二叉树的扩展

中英文查找测试代码

统计单词次数测试代码

1. 搜索二叉树

二叉搜索树概念

        二叉搜索树又称二叉排序树,也可以是一棵空树。对于搜索二叉树具有以下性质:

                1. 若左子树不为空,则左子树上所有结点的值都小于根节点的值;

                2. 若右子树不为空,则右子树上所有结点的值都大于根节点的值;

                3. 它的左右子树也分别是二叉搜索树。

        关于二叉搜索树为什么叫做二叉排序树,这是因为左子树小于根节点,右子树大于根节点(二叉搜索树中的元素默认不会重复),当我们使用中序遍历(左 中 右)的时候,遍历刚好出来是有序的。

二叉树类框架

        建立一颗二叉树,首先需要一个结点的类,然后我们需要使用一个根节点将其维护起来。如下:

template <class K>
struct BSTreeNode {
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;
	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

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

private:
	Node* _root;
};

搜索二叉树的插入

        关于搜索二叉树的插入,我们只需要找到合适的位置将其插入即可。也就是当我们需要插入的元素大于当前元素的时候,我们就继续往右子树放,反之放在左值树,直到到空结点的时候,我们还需要记录当前搜索结点的父亲结点,便于之后将其连接起来,我们就可以插入元素了。

        注:默认搜索二叉树不含有重复元素,所以当插入重复元素的时候,插入失败。

bool insert(const K& key) {
	if (_root == nullptr) {
		_root = new Node(key);
		return true;
	}
	// 左子树小于根节点,右子树大于根节点
	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 {
			return false;
		}
	}
	cur = new Node(key);
	if (key < parent->_key)
		parent->_left = cur;
	else
		parent->_right = cur;
	return true;
}

搜索二叉树的查找

        查找遵循搜索二叉树的性质,当需要查找的数小于当前结点的时候,我们往左子树查找,当需要查找的数大于当前结点的时候,我们往右边查找。若直到空结点都还没有查找到,那么就查找失败了。如下:

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

搜索二叉树的遍历

        搜索二叉树的遍历我们采用中序遍历,因为遍历出来的结果就是有序的。我们使用递归遍历。但是我们需要注意的一点是,我们在遍历的时候,需要访问到根节点,但是若我们在类外想要遍历的时候,我们并不能传一个被 private 保护的根节点的,所以我们需要进行如下的封装,就不需要进行传参了。如下:

template <class K>
class BSTree {
public:
	typedef BSTreeNode<K> Node;
    // 中序遍历
	void InOrder() {
		_InOrder(_root);
		cout << endl;
	}

private:
	void _InOrder(Node* root) {
		// 左中右
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root;
};

搜索二叉树的删除

        关于搜索二叉树结点的删除,会存在很多的情况,如:删除的位置是叶子结点,删除的位置左子树为空,删除的位置右子树为空,删除的位置左右子树都不为空,删除的位置为根节点,且左子树或右子树为空。

        所以搜索二叉树的删除实现较为复杂,首先需要找到该位置,若直到空结点都还未找到,则二叉树中并无该元素,删除失败,返回 false;

        若删除的位置是叶子结点,删除的位置左子树为空,删除的位置右子树为空:我们先讨论删除位置的左子树为空,那么删除位置右节点可能不为空,所以删除该位置之后需要将删除位置的父亲结点的指针(可能是左,也可能是右)指向删除结点的右子树。删除位置的右子树为空,那么删除位置左节点可能不为空,所以删除该位置之后需要将删除位置的父亲结点的指针(可能是左,也可能是右)指向删除结点的左子树。当我们实现以上的两种情况的时候,我们发现删除位置是叶子结点的问题也迎刃而解了。

        删除的位置左右子树都不为空:当我们删除位置的左子树和右子树都不为空的时候,我们就需要讨论一个问题,删除该位置之后,左右子树该如何进行连接?答案是找到左子树的最大节点(最右结点)或者找到右子树的最小结点(最左结点)将其替换即可,替换之后在将其删除即可,但是其中还有一个不可忽视的问题,当我们替换之后删除的位置并不是叶子结点的时候,又该如何进行连接呢?以替换删除的结点为右子树的最小结点为例子,我们需要将删除结点的父亲结点指向(可能是左指针也可能是右指针,需要判断)删除结点的右子树。

        删除的位置为根节点,且左子树或右子树为空:当需要删除的结点为根结点且一端的子树为空的时候,我们只需要将根节点往另一个相反的结点移位即可。如下:

        将会对每种情况在代码中注释:

bool erase(const K& key) {
	// 先寻找key,找到删除,没找到直接返回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 {
			break;
		}
	}
	if (cur == nullptr) return false;
    // 需要删除位置的左子树或右子树为空
	if (cur->_left == nullptr) {
        // 删除位置为根节点
		if (parent == nullptr) {
			parent = cur;
			_root = cur->_right;

			delete parent;
			return true;
		}
		else {
			if (parent->_right == cur)
				parent->_right = cur->_right;
			else
				parent->_left = cur->_right;
			delete cur;
		}
	}
	else if (cur->_right == nullptr) {
		if (parent == nullptr) {
			parent = cur;

			_root = cur->_left;
			delete parent;
			return true;
		}
		else {
			if (parent->_right == cur)
				parent->_right = cur->_left;
			else
				parent->_left = cur->_left;
			delete cur;
		}
	}
	else {
		// 删除左右子树都有元素的结点
		// 找到右边最小的
		Node* rightMin = cur->_right;
		Node* rightMinParent = cur;
		while (rightMin->_left) {
			rightMinParent = rightMin;
			rightMin = rightMin->_left;
		}
		// 现在的rightMin为右子树最小结点元素
		std::swap(cur->_key, rightMin->_key);
		// 若要删除的结点如父亲结点的左结点,链接左边
		if (rightMinParent->_right == rightMin)
			rightMinParent->_right = rightMin->_right;
		else
			rightMinParent->_left = rightMin->_right;
		delete rightMin;
	}
	return true;
}

搜索二叉树所有代码

template <class K>
struct BSTreeNode {
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
	K _key;
	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

template <class K>
class BSTree {
public:
	typedef BSTreeNode<K> Node;
	// 构造函数
	BSTree() : _root(nullptr) {}
	// 插入、删除、查找、遍历函数
	bool insert(const K& key) {
		if (_root == nullptr) {
			_root = new Node(key);
			return true;
		}
		// 左子树小于根节点,右子树大于根节点
		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 {
				return false;
			}
		}
		cur = new Node(key);
		if (key < parent->_key)
			parent->_left = cur;
		else
			parent->_right = cur;
		return true;
	}

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

	bool erase(const K& key) {
		// 先寻找key,找到删除,没找到直接返回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 {
				break;
			}
		}
		if (cur == nullptr) return false;
		// 现在的 cur 是我们需要删除的结点
		// 若该结点为根节点

		if (cur->_left == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				_root = cur->_right;

				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_right;
				else
					parent->_left = cur->_right;
				delete cur;
			}
		}
		else if (cur->_right == nullptr) {
			if (parent == nullptr) {
				parent = cur;

				_root = cur->_left;
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_left;
				else
					parent->_left = cur->_left;
				delete cur;
			}
		}
		else {
			// 删除左右子树都有元素的结点
			// 找到右边最小的
			Node* rightMin = cur->_right;
			Node* rightMinParent = cur;
			while (rightMin->_left) {
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
			}
			// 现在的rightMin为右子树最小结点元素
			std::swap(cur->_key, rightMin->_key);
			// 若要删除的结点如父亲结点的左结点,链接左边
			if (rightMinParent->_right == rightMin)
				rightMinParent->_right = rightMin->_right;
			else
				rightMinParent->_left = rightMin->_right;
			delete rightMin;
		}
		return true;
	}

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

private:
	void _InOrder(Node* root) {
		// 左中右
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
private:
	Node* _root;
};

int main() {
	BSTree<int> bs;
	vector<int> v({ 8, 3, 1, 10, 6, 4, 7, 14, 13 });
	for (auto e : v) {
		bs.insert(e);
	}
	bs.InOrder();
	bs.erase(1);
	for (auto e : v) {
		bs.erase(e);
		bs.InOrder();

	}
	bs.InOrder();

	return 0;
}
测试

 2. 搜索二叉树的扩展

        关于搜索二叉树一共存在两种模型,一种为 K 模型,另一种为 KV 模型,如下:

        K 模型:K 模型即只有 key 作为关键码,结构中只需要存储 key 即可,关键码即为需要搜索到的值。(也就是上文中实现的搜索二叉树)

        KV 模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对
        关于 KV 模型,比如:英汉词典就是英文与中文的对应关系统计单词次数,统计成功后,给定单词就可快速找到其出现的次数。

        关于 KV 模型的实现和 K 模型大同小异,如下:

template <class K, class V>
struct BSTreeNode {
	BSTreeNode<K, V>* _left;
	BSTreeNode<K, V>* _right;
	K _key;
	V _value;
	BSTreeNode(const K& key, const V& value)
		:_left(nullptr)
		,_right(nullptr)
		,_key(key)
		,_value(value)
	{}
};

template <class K, class V>
class BSTree {
public:
	typedef BSTreeNode<K, V> Node;
	// 构造函数
	BSTree() : _root(nullptr) {}
	// 插入、删除、查找、遍历函数
	bool insert(const K& key, const V& value) {
		if (_root == nullptr) {
			_root = new Node(key, value);
			return true;
		}
		// 左子树小于根节点,右子树大于根节点
		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 {
				return false;
			}
		}
		cur = new Node(key, value);
		if (key < parent->_key)
			parent->_left = cur;
		else
			parent->_right = cur;
		return true;
	}

	Node* find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key > cur->_key) {
				cur = cur->_right;
			}
			else if (key < cur->_key) {
				cur = cur->_left;
			}
			else {
				return cur;
			}
		}
		return nullptr;
	}
	
	bool erase(const K& key) {
		// 先寻找key,找到删除,没找到直接返回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 {
				break;
			}
		}
		if (cur == nullptr) return false;
		// 现在的 cur 是我们需要删除的结点
		// 若该结点为根节点

		if (cur->_left == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				_root = cur->_right;
				
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_right;
				else
					parent->_left = cur->_right;
				delete cur;
			}
		}
		else if (cur->_right == nullptr) {
			if (parent == nullptr) {
				parent = cur;
				
				_root = cur->_left;
				delete parent;
				return true;
			}
			else {
				if (parent->_right == cur)
					parent->_right = cur->_left;
				else
					parent->_left = cur->_left;
				delete cur;
			}
		}
		else {
			// 删除左右子树都有元素的结点
			// 找到右边最小的
			Node* rightMin = cur->_right;
			Node* rightMinParent = cur;
			while (rightMin->_left) {
				rightMinParent = rightMin;
				rightMin = rightMin->_left;
			}
			// 现在的rightMin为右子树最小结点元素
			std::swap(cur->_key, rightMin->_key);
			// 若要删除的结点如父亲结点的左结点,链接左边
			if(rightMinParent->_right == rightMin)
				rightMinParent->_right = rightMin->_right;
			else
				rightMinParent->_left = rightMin->_right;
			delete rightMin;
		}
		return true;
	}

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

private:
	void _InOrder(Node* root) {
		// 左中右
		if (root == nullptr) return;
		_InOrder(root->_left);
		cout << root->_key << " " << root->_value << endl;
		_InOrder(root->_right);
	}
private:
	Node* _root;
};
中英文查找测试代码
int main() {
	BSTree<string, string> bs;
	string s1 = "insert";
	string v1 = "插入";
	string s2 = "right";
	string v2 = "右边";
	string s3 = "left";
	string v3 = "左边";
	bs.insert(s1, v1);
	bs.insert(s2, v2);
	bs.insert(s3, v3);
	string s;
	while (cin >> s) {
		if (bs.find(s))
			cout << bs.find(s)->_value << endl;
		else
			cout << "没有该单词的含义" << endl;
	}
	return 0;
}
统计单词次数测试代码
int main() {
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	BSTree<string, int> bs;
	for (auto& str : arr) {
		auto ret = bs.find(str);
		if (ret == nullptr)
			bs.insert(str, 1);
		else
			ret->_value++;
	}
	bs.InOrder();
	return 0;
}

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

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

相关文章

决策树与机器学习实战【代码为主】

文章目录 &#x1f6f4;&#x1f6f4;引言&#x1f6f4;&#x1f6f4;决策树使用案例&#x1f6f4;&#x1f6f4;numpy库生成模拟数据案例&#x1f6f4;&#x1f6f4;决策树回归问题&#x1f6f4;&#x1f6f4;决策树多分类问题 &#x1f6f4;&#x1f6f4;引言 决策树是一种经…

Https自签名证书

openSSL下载 https://slproweb.com/products/Win32OpenSSL.html 1_整体流程 &#xff08;1&#xff09;https介绍 HTTPS 是 Hypertext Transfer Protocol Secure 的简称&#xff0c;是基于 SSL 加密方式的 HTTP 协议 &#xff08;2&#xff09;CA机构介绍 介绍&#xff1a…

PHP之fastadmin系统配置分组增加配置和使用

目录 一、实现功能&#xff1a;fasttadmin实现添加系统配置分组和添加参数、使用 二、添加分组 三、配置分组参数 四、最终存储位置 五、获取配置参数 一、实现功能&#xff1a;fasttadmin实现添加系统配置分组和添加参数、使用 二、添加分组 在字典配置中找到分组对应键值…

高效掌控速卖通自养号测评:成本、步骤、技巧全方位掌握

在跨境电商的汹涌浪潮中&#xff0c;速卖通犹如一颗璀璨的领航星&#xff0c;引领着无数寻求海外拓展的企业和商家驶向国际市场的广阔海域。从最初的C2C模式起步&#xff0c;速卖通历经蜕变&#xff0c;如今已华丽转身成为B2C跨境电商领域的翘楚&#xff0c;承载着无数中国卖家…

新业务 新市场 | 灵途科技新品亮相马来西亚亚洲防务展

5月6日&#xff0c;灵途科技携新品模组与武汉长盈通光电&#xff08;股票代码&#xff1a;688143&#xff09;携手参加第18届马来西亚亚洲防务展。首次亮相海外&#xff0c;灵途科技便收获全球客户的广泛关注&#xff0c;为公司海外市场开拓打下坚实基础。 灵途科技与长盈通共同…

探索Facebook:数字社交的新时代

Facebook&#xff0c;作为全球最大的社交网络平台之一&#xff0c;一直在引领着数字社交的发展潮流。随着科技的不断进步和社会的不断变迁&#xff0c;Facebook也在不断演进和创新&#xff0c;迎接着数字社交的新时代。本文将探索Facebook在数字社交领域的新发展&#xff0c;以…

fpga系列 HDL: 05 阻塞赋值(=)与非阻塞赋值(<=)

在Verilog硬件描述语言&#xff08;HDL&#xff09;中&#xff0c;信号的赋值方式主要分为两种&#xff1a;连续赋值和过程赋值。每种赋值方式有其独特的用途和语法&#xff0c;并适用于不同类型的电路描述。 1. 连续赋值&#xff08;Continuous Assignment,assign 和&#xf…

pycharm中无法激活conda虚拟环境

在windwos的cmd命令行中能激活&#xff0c;但是pycharm中无法激活虚拟环境&#xff0c;报错提示&#xff1a; 后来发现pycharm默认命令行是powershell&#xff0c;改成cmd重启pycharm命令行即可

windows安全配置

环境&#xff1a;本文我们以Windows Server 2012 R2为例&#xff0c;进行加固 Windows 不论什么版本&#xff0c;进行安全配置均包含以下两个常用维度 1、账户策略 ①密码策略 强制密码历史&#xff0c;建议设置为24个 密码最长使用期限&#xff0c;建议设置60天 密码最短…

Excel函数之SCAN

SCAN 语法 SCAN([initial_value], array, lambda(accumulator,value,calculation)) initial_value:用于设置累加器的初始值,即accumulator的初始值,该值为空时,array的首个值将直接赋值给累加器,并且该值将不参与函数运算array:需要进行循环计算的数组accumulator:累加…

HTML5 基本框架

HTML5基本的内容 文章目录 系列文章目录前言一、HTML5 基本框架二、具体框架结构三、知识补充总结 前言 HTML5的介绍&#xff1a; HTML5 是一种用于构建网页内容的标准化语言。它是 HTML&#xff08;超文本标记语言&#xff09;的第五个版本&#xff0c;引入了许多新的功能和特…

wordpress主题给网站增加一个版权声明区块代码分享

在数字化时代&#xff0c;网络上的信息传播变得越来越便捷&#xff0c;给人们生活和工作带来了极大的便利。然而&#xff0c;在这个过程中也产生了很多版权问题。为了更好地保护自己的版权&#xff0c;许多网站开始在其网页上添加版权声明。本文将探讨在网站上添加版权声明的重…

PageHelper分页查询时,count()查询记录总数与实际返回的数据数量不一致

目录 场景简介代码判断异常情况排查原因解决 场景简介 1、使用PageHelper进行分页查询 2、最终构建PageInfo对象时&#xff0c;total与实际数据量不符 代码判断 异常情况 排查 通过对比count()查询的SQL与查询记录的SQL&#xff0c;发现是PageHelper分页查询时省去了order b…

Object类——toString方法和equals方法

前言&#xff1a; 在java中&#xff0c;所有类都是有继承关系存在的&#xff0c;都默认继承Object类。当一个类继承了其他父类&#xff0c;它并不会直接继承Object类&#xff0c;但是它的父类若是没有其他继承关系也会默认继承Object类&#xff0c;子类也可以继续调用Object类…

无线麦克风哪个品牌音质最好,揭示麦克风什么牌子的音质效果好!

​随着科技的不断发展&#xff0c;无线领夹麦克风已经成为现代演讲、演出和采访中不可或缺的工具。这种小巧便携的设备&#xff0c;能够让我们摆脱线缆的束缚&#xff0c;自由地在舞台上或讲台上移动&#xff0c;同时保持声音的清晰和稳定。在这篇文章中&#xff0c;我们将介绍…

基于Ubuntu的Bash脚本实现SystemUI的编译真机验证

使用场景描述 当开发SystemUI的时候&#xff0c;开发完一个需求后需要到真机上验证&#xff0c;虽然SystemUI模块开发最后的产物也是APK&#xff0c;但是这个APK 却不能单独安装查看效果&#xff0c;因为SystemUI是系统级别的应用&#xff0c;需要放置到系统指定的目录下。这时…

牛客热题:最小的k个数

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;力扣刷题日记 &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 文章目录 牛客热题&#xff1a;最小的k个数题目链接方法一&…

三轴加速度计M-A352AD实现实时的动态监测

地震监测设备如何快速监测到地震波的发生?如何快速地将地震信号传输到系统或设备上&#xff0c;让人快速做出相应对策?如何在恶劣的环境下&#xff0c;仍能保持稳定可靠的监测?其核心之一就是采用了传感器技术和相关设备&#xff0c;我们可以在地震易发生区域或重点观察的区…

人大金仓 KingBase查询死锁,释放死锁

人大金仓(kingbase)查询数据库死锁及释放 kingbase锁表排查以及释放锁 总结下 -- 查询&#xff0c;可自己添加where条件 SELECT * FROM sys_stat_activity WHERE state ! idle AND wait_event_typeLock-- 结束进程 SELECT sys_terminate_backend(pid);

学习Java的日子 Day49 函数,DOM

Day48 1.流程控制语句 if else for for-in(遍历数组时&#xff0c;跟Java是否一样) While do while break 语句用于跳出循环 continue 用于跳过循环中的一个迭代 2.函数 2.1 JavaScript 函数语法 函数就是包裹在花括号中的代码块&#xff0c;前面使用了关键词 function funct…