广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++数据结构二叉搜索树的实现应用与分析
  • 124
分享到

C++数据结构二叉搜索树的实现应用与分析

2024-04-02 19:04:59 124人浏览 安东尼
摘要

目录?概念?二叉搜索树的实现?基本框架?二叉搜索树的插入?二叉搜索树的查找?二叉搜索树的删除(重点)?二叉搜索树的应用?二叉树性能分析?总结⭐️博客代码已上传至gitee:https

⭐️博客代码已上传至gitee:https://gitee.com/byte-binxin/cpp-class-code

?概念

二叉搜索树又称为二叉排序书,因为这棵树的中序遍历是有序的。二叉搜索树总结起来有以下几个性质:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于于根节点的值
  • 它的左右子树都是二叉搜索树
  • 这棵树中没有重复的元素

?二叉搜索树的实现

?基本框架

由一个节点的成员构成,先构建节点的类型,和我们之前数据结构中的二叉树的节点定义是一样的。二叉搜索树的根节点先默认给空。


template <class K, class V>
struct BSTnode
{
	BSTNode<K, V>* _left;
	BSTNode<K, V>* _right;
	K _key;
	V _value;

	BSTNode(const K& key, const V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{}
};
template <class K, class V>
class BSTree //Binary Search Tree
{
	typedef BSTNode<K, V> Node;
private:
	Node* _root = nullptr;
};

?二叉搜索树的插入

插入分为下面几个步骤:

  • 先判断树是否为空,为空就让要插入的这个节点作为根节点,然后结束
  • 部署就确定要插入节点的位置
  • 用一个cur记录当前节点,parent记录父节点
  • 要插入节点的值如果比当前节点的值小,cur就往左走,如果比当前节点的值大,就往右子树走,如果等于就返回false,表面这棵树中有这个数据,不需要插入。

下面是一个简单的动图演示

请添加图片描述

注意: 这里不用担心新插入节点会在树中间插入,它一定是在最下面插入的,它会走到最下面,然后在树的底部插入。

代码实现如下:


bool Insert(const K& key, const V& value)
{
	// 没有节点时第一个节点就是根节点
	if (_root == nullptr)
	{
		_root = new Node(key, value);
		return true;
	}

	// 用一个父亲节点记录cur的上一个节点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		parent = cur;
		// 小于往左边走
		if (key < cur->_key)
			cur = cur->_left;
		else if (key > cur->_key)
			cur = cur->_right;
		else
			return false;// 已有的节点不插入,此次插入失败
	}

	cur = new Node(key, value);
	// 判断应该插在父节点的左边还是右边
	if (cur->_key < parent->_key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	return true;
}

为了更好地观察这棵树插入后是否有效,我们可以实现一个中序遍历,将其打印出来。 中序遍历代码如下:


void InOrder()
{
	// 利用子函数遍历
	_InOrder(_root);
	cout << endl;
}
void _InOrder(Node* root)
{
	if (root == nullptr)
		return;

	_InOrder(root->_left);
	cout << root->_key << ":" << root->_value << endl;
	_InOrder(root->_right);
}

测试代码如下:


void TestBSTree()
{
	BSTree<int> bt;
	int arr[] = { 5,3,4,1,7,8,2,6,0,9 };
	//int arr[] = { 1,2,3,4 };
	//int arr[] = { 4,3,2,1};
	for (auto e : arr)
	{
		bt.Insert(e);
	}

	bt.InOrder();
}

代码运行结果如下:

?二叉搜索树的查找

查找的步骤如下:(和插入的步骤有些类似)

  • 如果查找值key比当前节点的值小,就往左子树走
  • 如果查找值key比当前节点的值大,就往右子树走
  • 如果查找值key和当前节点的值相等,就返回当前节点的指针

代码实现如下:


Node* Find(const K& key)
{
	if (_root == nullptr)
		return nullptr;
	Node* cur = _root;
	while (cur)
	{
		// 小于往左边走
		if (key < cur->_key)
			cur = cur->_left;
		else if (key > cur->_key)
			cur = cur->_right;
		else
			return cur;
	}

	return nullptr;
}

?二叉搜索树的删除(重点)

二叉搜索树的删除相对来说会复杂一些,下面我要给大家分析一下。 有四种情况 先看下面这棵树,分别对以下四个节点进行删除会发生什么(如何处理)?

  • 删除节点1时,它的左右都为空,可以直接删除
  • 删除节点2时,它的左不为空右为空,删除方法如下:

还要分析一种特殊的情况,就是此时2没有父亲节点,也就是自己为根时,看下面如何操作

  • 删除节点7时,它的左为为右不为空,删除方法如下:

和情况2一样,该节点如果为根节点,就让自己的右孩子变成根节点。

  • 左右都不为空(替代法)

这种情况我们采用替代法来解决,替代法就是找一个节点和现在这个节点交换,然后转移为上面的情况,具体如下: 我们可以选择用左子树的最右节点(左子树最大的节点)或右子树的最左节点(右子树的最小节点)和当前节点互换,然后删除互换后的节点,这里我们统一采用用右子树的最右节点来进行替换。

然后这里可以转化为情况3来对节点进行删除,因为所有的最左孩子一定是左为空,右是不确定的。

总结: 一共有四种情况,但是情况1可以归为情况3,因为它也是左为空,所以整体处理下来是三种情况。

代码实现如下:


bool Erase(const K& key)
{
		// 如果树为空,删除失败
		if (_root == nullptr)
			return false;

		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			// 小于往左边走
			if (key < cur->_key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (key > cur->_key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// 找到了,开始删除
				// 1.左右子树都为空 直接删除  可以归类为左为空
				// 2.左右子树只有一边为空  左为空,父亲指向我的右,右为空,父亲指向我的左  
				// 3.左右子树都不为空  取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除
				if (cur->_left == nullptr)
				{
					// 要删除节点为根节点时,直接把右子树的根节点赋值给——root
					// 根节点的话会导致parent为nullptr
					if (_root == cur)
					{
						_root = _root->_right;
					}
					else
					{
						// 左为空,父亲指向我的右
						// 判断cur在父亲的左还是右
						if (parent->_left == cur) // cur->_key < parent->_key
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;
					}

					delete cur;
					cur = nullptr;
				}
				else if (cur->_right == nullptr)
				{
					if (_root == cur)
					{
						_root = _root->_left;
					}
					else
					{
						// 右为空,父亲指向我的左
						// 判断cur在父亲的左还是右
						if (parent->_left == cur)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;
					}

					delete cur;
					cur = nullptr;
				}
				else
				{
					// 找右子树中最小的节点
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;// 去右子树找
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//swap(cur->_key, rightMin->_key);
					// 替代删除
					cur->_key = rightMin->_key;

					// 转换成了第一种情况  左为空
					if (rightMinParent->_left == rightMin)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;


					delete rightMin;
					rightMin = nullptr;
				}
				return true;
			}
		}

		return false;
	}

测试代码如下:(要测试每种情况,还有测试删空的情况)


void TestBSTree()
{
	BSTree<int> bt;
	int arr[] = { 5,3,4,1,7,8,2,6,0,9 };
	for (auto e : arr)
	{
		cout << "插入 " << e << " 后:";
		bt.Insert(e);
		bt.InOrder();
	}
	
	cout << "------------------------------" << endl;
	for (auto e : arr)
	{
		cout << "删除 " << e << " 后:";
		bt.Erase(e);
		bt.InOrder();
	}

}

代码运行结果如下:

?二叉搜索树的应用

二叉搜索树有两种模型:

  • K模型: K模型只有key值,节点只存储key值。这里主要应用就是查找判断某个元素在不在。
  • KV模型: KV模型每个key值都对应着一个value,主要应用就是通过key找value。(我们平时查找单词就是通过中文找英文,或者通过英文找中文)

下面我把上面的K模型的代码简单改造一下,实现KV模型:(这里没有使用传键值对的方法,之后的博客我会给大家介绍,这里使用传两个值的方式)


template <class K, class V>
struct BSTNode
{
BSTNode<K, V>* _left;
BSTNode<K, V>* _right;
K _key;
V _value;

BSTNode(const K& key, const V& value)
	:_left(nullptr)
	, _right(nullptr)
	, _key(key)
	,_value(value)
{}
};
template <class K, class V>
class BSTree //Binary Search Tree
{
typedef BSTNode<K, V> Node;
public:
~BSTree()
{
	Node* cur = _root;
	while (cur)
	{
		Erase(cur->_key);
		cur = _root;
	}
}
Node* Find(const K& key)
{
	if (_root == nullptr)
		return nullptr;
	Node* cur = _root;
	while (cur)
	{
		// 小于往左边走
		if (key < cur->_key)
			cur = cur->_left;
		else if (key > cur->_key)
			cur = cur->_right;
		else
			return cur;
	}

	return nullptr;
}
bool Insert(const K& key, const V& value)
{
	// 没有节点时第一个节点就是根节点
	if (_root == nullptr)
	{
		_root = new Node(key, value);
		return true;
	}

	// 用一个父亲节点记录cur的上一个节点
	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		parent = cur;
		// 小于往左边走
		if (key < cur->_key)
			cur = cur->_left;
		else if (key > cur->_key)
			cur = cur->_right;
		else
			return false;// 已有的节点不插入,此次插入失败
	}

	cur = new Node(key, value);
	// 判断应该插在父节点的左边还是右边
	if (cur->_key < parent->_key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}

	return true;
}
bool Erase(const K& key)
{
	// 如果树为空,删除失败
	if (_root == nullptr)
		return false;

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		// 小于往左边走
		if (key < cur->_key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (key > cur->_key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			// 找到了,开始删除
			// 1.左右子树都为空 直接删除  可以归类为左为空
			// 2.左右子树只有一边为空  左为空,父亲指向我的右,右为空,父亲指向我的左  
			// 3.左右子树都不为空  取左子树最大的节点或右子树最小的节点和要删除的节点交换,然后再删除
			if (cur->_left == nullptr)
			{
				// 要删除节点为根节点时,直接把右子树的根节点赋值给——root
				// 根节点的话会导致parent为nullptr
				if (_root == cur)
				{
					_root = _root->_right;
				}
				else
				{
					// 左为空,父亲指向我的右
					// 判断cur在父亲的左还是右
					if (parent->_left == cur) // cur->_key < parent->_key
						parent->_left = cur->_right;
					else
						parent->_right = cur->_right;
				}

				delete cur;
				cur = nullptr;
			}
			else if (cur->_right == nullptr)
			{
				if (_root == cur)
				{
					_root = _root->_left;
				}
				else
				{
					// 右为空,父亲指向我的左
					// 判断cur在父亲的左还是右
					if (parent->_left == cur)
						parent->_left = cur->_left;
					else
						parent->_right = cur->_left;
				}

				delete cur;
				cur = nullptr;
			}
			else
			{
				// 找右子树中最小的节点
				Node* rightMinParent = cur;
				Node* rightMin = cur->_right;// 去右子树找
				while (rightMin->_left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}
				//swap(cur->_key, rightMin->_key);
				// 替代删除
				cur->_key = rightMin->_key;

				// 转换成了第一种情况  左为空
				if (rightMinParent->_left == rightMin)
					rightMinParent->_left = rightMin->_right;
				else
					rightMinParent->_right = rightMin->_right;


				delete rightMin;
				rightMin = nullptr;
			}
			return true;
		}
	}

	return false;
}
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 = nullptr;
};

void TestBSTree_KV1()
{
// 创建一个简易的字典
BSTree<string, string> dict;

dict.Insert("苹果", "apple");
dict.Insert("排序", "sort");
dict.Insert("培养", "cultivate");
dict.Insert("通过", "pass");
dict.Insert("apple", "苹果");
dict.Insert("sort", "排序");
dict.Insert("cultivate", "培养");
dict.Insert("pass", "通过");

string str;
while (cin >> str)
{
	BSTNode<string, string>* ret = dict.Find(str);
	if (ret)
	{
		cout << ret->_value << endl;
	}
	else
	{
		cout << "本字典无此词" << endl;
	}
}

下面测试几个应用: 实例1 英汉字典


void TestBSTree_KV1()
	{
		// 创建一个简易的字典
		BSTree<string, string> dict;

		dict.Insert("苹果", "apple");
		dict.Insert("排序", "sort");
		dict.Insert("培养", "cultivate");
		dict.Insert("通过", "pass");
		dict.Insert("apple", "苹果");
		dict.Insert("sort", "排序");
		dict.Insert("cultivate", "培养");
		dict.Insert("pass", "通过");

		string str;
		while (cin >> str)
		{
			BSTNode<string, string>* ret = dict.Find(str);
			if (ret)
			{
				cout << ret->_value << endl;
			}
			else
			{
				cout << "本字典无此词" << endl;
			}
		}
	}

代码运行结果演示:

实例2: 统计树


void TestBSTree_KV2()
{
	// 统计水果个数
	BSTree<string, int> countTree;

	string strArr[] = { "香蕉","水蜜桃","西瓜","苹果","香蕉" ,"西瓜","香蕉" ,"苹果","西瓜","苹果","苹果","香蕉" ,"水蜜桃" };

	for (auto e : strArr)
	{
		BSTNode<string, int>* ret = countTree.Find(e);
		if (ret == nullptr)
		{
			// 第一次插入
			countTree.Insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}

	countTree.InOrder();
}

代码运行结果如下:

?二叉树性能分析

一般情况下,二叉搜索树的插入和删除的效率都是O(logN),极端情况会导致效率变成O(N)。

理想状态: 完全二叉树:O(logN)

极端情况: 一条链:O(1)

后面我要和大家分析的AVL树会利用旋转,就可解决掉这种极端情况。

?总结

上面这些是二叉搜索树的大致内容,其中删除大家可以好好理解一下,它后面还有两棵树我还没有介绍,就是AVL树和红黑树,在后面两篇博客我都会介绍。今天就先到这了,喜欢的话,欢迎点赞支持~

到此这篇关于c++数据结构二叉搜索树的实现应用与分析的文章就介绍到这了,更多相关C++ 二叉搜索树内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: C++数据结构二叉搜索树的实现应用与分析

本文链接: https://www.lsjlt.com/news/140805.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • C++数据结构二叉搜索树的实现应用与分析
    目录概念二叉搜索树的实现基本框架二叉搜索树的插入二叉搜索树的查找二叉搜索树的删除(重点)二叉搜索树的应用二叉树性能分析总结⭐️博客代码已上传至gitee:https://gitee....
    99+
    2022-11-13
  • C++数据结构之搜索二叉树的实现
    目录零.前言1.概念2.作用3.迭代实现(1)查找(2)插入(3)删除4.递归实现(1)查找(2)插入(3)删除5.key/value模型的应用(1)对应查找(2)判断出现次数6.总...
    99+
    2022-11-13
  • Java数据结构之二叉搜索树实例分析
    这篇文章主要介绍“Java数据结构之二叉搜索树实例分析”,在日常操作中,相信很多人在Java数据结构之二叉搜索树实例分析问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java数据结构之二叉搜索树实例分析”的疑...
    99+
    2023-06-30
  • C++数据结构之二叉搜索树的实现详解
    目录前言介绍实现节点的实现二叉搜索树的查找二叉搜索树的插入二叉搜索树的删除总结前言 今天我们来学一个新的数据结构:二叉搜索树。 介绍 二叉搜索树也称作二叉排序树,它具有以下性质: 非...
    99+
    2022-11-13
  • Java数据结构超详细分析二叉搜索树
    目录1.搜索树的概念2.二叉搜索树的简单实现2.1查找2.2插入2.3删除2.4修改3.二叉搜索树的性能 1.搜索树的概念 二叉搜索树是一种特殊的二叉树,又称二叉查找树,二叉排序树,...
    99+
    2022-11-13
  • C++详解数据结构中的搜索二叉树
    目录定义查找某个元素构造搜索二叉树往搜索二叉树中插入元素搜索二叉树删除节点定义 搜索二叉树,也称有序二叉树,排序二叉树,是指一棵空树或者具有下列性质的二叉树: 1、若任意节点的左子树...
    99+
    2022-11-13
  • 【Java 数据结构】实现一个二叉搜索树
    目录  1、认识二叉搜索树 2、实现一个二叉搜索树 2.1 成员变量 2.2 insert 方法 2.3 search 方法  2.4 remove 方法(重点) 3、二叉搜索树总结 1、认识二叉搜索树 从字面上来看,它只比二叉树多...
    99+
    2023-09-02
    数据结构 算法 二叉搜索树
  • C++简单实现与分析二叉搜索树流程
    目录二叉搜索树二叉搜索树的重要操作二叉搜索树实现(key模型)二叉搜索树的应用二叉搜索树的实现(key/value模型)二叉搜索树 二叉搜索树又被称为二叉排序树。它可以是一个空树,如...
    99+
    2022-11-13
  • C++使用LeetCode实现二叉搜索树的示例分析
    这篇文章将为大家详细讲解有关C++使用LeetCode实现二叉搜索树的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Given an integer n, generate all st...
    99+
    2023-06-20
  • C语言实现二叉搜索树的完整总结
    目录1、 二叉树的构建2、二叉树的遍历前序遍历中序遍历后序遍历层序遍历4、二叉树的高度5、二叉树的删除6、由几种遍历序列还原二叉树 前序序列、中序序列还原二叉树:中序序列、...
    99+
    2022-11-12
  • C语言数据结构系列篇二叉树的概念及满二叉树与完全二叉树
    链接:C语言数据结构系列之树的概念结构和常见表示方法 0x00 概念 定义:二叉树既然叫二叉树,顾名思义即度最大为2的树称为二叉树。 它的度可以为 1 也可...
    99+
    2022-11-13
  • Java数据结构之线索化二叉树的实现
    目录1.线索化二叉树的介绍2.线索化二叉树的基本特点3.线索化二叉树的应用案例4.前序线索化二叉树、遍历5.后序线索化二叉树1.线索化二叉树的介绍 将数列 {1, 3, 6, 8, ...
    99+
    2022-11-13
  • C++使用LeetCode实现独一无二的二叉搜索树
    这篇文章主要介绍C++使用LeetCode实现独一无二的二叉搜索树,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完![LeetCode] 96. Unique Binary Search Trees 独一无二的二叉搜索树...
    99+
    2023-06-20
  • C语言堆与二叉树的顺序结构与实现
    目录一. 二叉树的顺序结构二. 堆的概念及结构三. 堆的实现四. 堆排序(具有缺陷型)一. 二叉树的顺序结构 普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全...
    99+
    2022-11-13
  • JavaScript数据结构与算法之二叉树插入节点、生成二叉树的示例分析
    小编给大家分享一下JavaScript数据结构与算法之二叉树插入节点、生成二叉树的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解...
    99+
    2022-10-19
  • Java数据结构之线索化二叉树怎么实现
    这篇文章主要介绍“Java数据结构之线索化二叉树怎么实现”,在日常操作中,相信很多人在Java数据结构之线索化二叉树怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java数据结构之线索化二叉树怎么实现...
    99+
    2023-06-30
  • C语言数据结构详细解析二叉树的操作
    目录二叉树分类二叉树性质性质的使用二叉树的遍历前序遍历中序遍历后序遍历层序遍历求二叉树的节点数求二叉树叶子结点个数求二叉树的最大深度二叉树的销毁二叉树分类 满二叉树 除最后一层无任何...
    99+
    2022-11-13
  • Java数据结构之平衡二叉树的原理与实现
    目录1 平衡二叉树的概述2 平衡二叉树的实现原理2.1 单旋转2.2 双旋转2.3 总结3 平衡二叉树的构建3.1 类架构3.2 查找的方法3.3 检查是否平衡的方法3.4 插入的方...
    99+
    2022-11-13
  • Java数据结构之二叉排序树的实现
    目录1 二叉排序树的概述2 二叉排序树的构建2.1 类架构2.2 查找的方法2.3 插入的方法2.4 查找最大值和最小值2.5 删除的方法3 二叉排序树的总结1 二叉排序树的概述 本...
    99+
    2022-11-13
  • Java数据结构之二叉查找树的实现
    目录定义节点结构查找算法插入算法删除算法完整代码定义 二叉查找树(亦称二叉搜索树、二叉排序树)是一棵二叉树,且各结点关键词互异,其中根序列按其关键词递增排列。 等价描述:二叉查找树中...
    99+
    2022-11-13
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作