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

        本篇将会讲解有关二叉树的搜索原理,以及关于二叉搜索树的建立,以及二叉树搜索树的插入、删除和查找等基本操作。最后我们还会对二叉搜索树进行功能扩展,介绍有关搜索二叉树的 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

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

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

香橙派 AIpro开发板:开启AI视觉的无限可能

前言 在当今这个由数据和智能驱动的时代&#xff0c; 人工智能&#xff08;AI&#xff09; 已经成为推动技术创新和实现自动化的关键。 特别是在计算机视觉领域&#xff0c;AI的潜能被无限放大&#xff0c;它使得机器能够“看见”并理解视觉世界&#xff0c;从而执行复杂的任务…

安捷伦Agilent 8114A脉冲发生器的特点资料

Agilent 8114A 脉冲发生器有助于测试当今的电信和计算机系统组件&#xff0c;这些组件越来越多地利用在高电压或大电流下运行的激光和红外二极管、EEPROMS 和闪存等设备。 Agilent 8114A 高功率脉冲发生器的特点包括&#xff1a; 频率高达 15 MHz 时&#xff0c;高达 100 V 的…

前端 CSS 经典:图片边框

前言&#xff1a;有这么一个业务&#xff0c;需要边框随着图片宽度的变化而变化&#xff0c;比如一些聊天的气泡框等。 实现原理&#xff1a;使用 border-image 属性 效果图&#xff1a; 实现代码&#xff1a; <!DOCTYPE html> <html lang"en"><he…

Qt/C++音视频开发75-获取本地有哪些摄像头名称/Qt内置函数方式

一、前言 在需要打开本地摄像头的场景中&#xff0c;有个需求绕不开&#xff0c;那就是如何获取本地有哪些摄像头设备名称&#xff0c;这样可以提供下拉框给用户选择&#xff0c;不然你让用户去填设备名&#xff0c;你觉得用户会知道是啥&#xff0c;他会操作吗&#xff1f;就…

[猫头虎分享21天微信小程序基础入门教程] 第17天:小程序的用户授权与安全

[猫头虎分享21天微信小程序基础入门教程] 第17天&#xff1a;小程序的用户授权与安全 第17天&#xff1a;小程序的用户授权与安全 &#x1f512; 自我介绍 大家好&#xff0c;我是猫头虎&#xff0c;一名全栈软件工程师。今天我们继续微信小程序的学习&#xff0c;重点了解如…

【C++】Vector的简易模拟与探索

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

【C++初阶】auto关键字

目录 1.auto简介 2.auto的使用 1.auto简介 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量&#xff0c;但遗憾的 是一直没有人去使用它&#xff0c;大家可思考下为什么&#xff1f; C11中&#xff0c;标准委员会赋予了auto全…

Go语言

Go语言 Go语言全称Golanguage&#xff0c;Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译并发型语言。于2009年首次发布 官网 特点 简单易学&#xff1a;Go语言语法简洁明了&#x…

【AD21】原理图PDF文件的输出

原理图PDF文件可以共享给团队成员&#xff0c;用于设计审核、讨论和协同工作。 菜单栏中点击文件->智能PDF。 在弹出的界面点击Next&#xff0c;勾选当前项目&#xff0c;修改文件名&#xff0c;避免与制造装备图PDF文件重名将其覆盖&#xff0c;点击Next。 只输出原理图…

SmartEDA革新电路设计,效率飙升,Multisim与Proteus迎来强劲对手!

在电路设计领域&#xff0c;Multisim和Proteus一直以其强大的仿真功能和广泛的应用范围受到设计师们的青睐。然而&#xff0c;随着科技的不断进步和创新&#xff0c;一款名为SmartEDA的新兴软件正以其独特的优势&#xff0c;重新定义着电路设计的效率。 SmartEDA的崛起&#x…

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

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

这13个前端库,帮我在工作中赢得了不少摸鱼时间

前言 平时开发的过程中&#xff0c;常常会使用到一些第三方库来提高开发效率&#xff0c;我总结了自己工作这么久以来经常用到的 13 个库&#xff0c;希望对大家有帮助&#xff5e; antd 全称应该是Ant Design&#xff0c;这是一个 React 的组件库&#xff0c;旨在提供一套常…

Android Studio 中gradle的bin和all区别

1.在android studio中设置安装gradle时&#xff0c;真各种版本看到眼花缭乱&#xff0c;还有疑惑gradle-*.*-all.zip与gradle-*.*-bin.zip的区别是什么。下面解压如下: bin&#xff1a; all: 其实&#xff0c;用bin就可以了&#xff0c;all文件就是多了docs(文档)和src(源码)两…

本周日晚8点预约宣讲会 | 深入了解项目,开启你的开源之旅!

引言 社区的亲爱的同学们&#xff01;为了帮助大家在这个夏天更好的参加“开源之夏”的活动&#xff0c;我们联合2位资深开源项目导师&#xff0c;给大家策划了这次“开源之夏”宣讲会。 这不仅是一个了解如何参与开源项目的机会&#xff0c;更是一个直接与项目导师面对面交流…

华火硬核专利库丨登创新科技之巅,探创新未至之境

十年的艰苦卓越&#xff0c;“灶”就了华火科技之巅&#xff1b;电生明火的应用&#xff0c;不仅是一次颠覆性的创新&#xff0c;更是对未来厨房的无尽遐想与探索。在当今日新月异的科技时代&#xff0c;创新已成为推动社会进步的重要动力。 华火烹饪科技&#xff0c;以其深厚的…

Unity 直线间隔放置物体

直线间隔放置物体 0. 新建一个空物体&#xff0c;挂上脚本ZYF_QuickPlaceObj 设置 间隔距离 和 预制体在Scene中拖动即可按间隔距离实例化物体物体的朝向始终朝向统一方向&#xff0c;并且可以在Scene中拖拽更改 传送门

Object类——toString方法和equals方法

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

深度学习——图像分类(CNN)—测试模型

测试模型 1.导入必要的库2.加载测试数据集3.假设CSV文件中的图像文件名是完整的路径4.随机选择一张图片进行展示5.加载图像6.使用模型进行预测7.设置模型的预测结果8.计算准确率9.指定test文件夹路径10.读取名为image_path的图片11.加载图像12.检查图像是否为空 训练的模型是上…

Easy IP + DNAT(服务器NAT转换)

第一章 Easy IP 1.1 一般家庭和企业使用的地址转换方式 直接使用出接口的地址做转换Easy IP适用于小规模居于网中的主机访问Internet的场景如&#xff1a;家庭、小型网吧、小型办公室中&#xff0c;这些地方内部主机不多&#xff0c;出接口可以通过拨号方式获取一个临时公网I…