C++进阶——二叉搜索树

1.二叉搜索树的概念及介绍

二叉搜索树是在二叉树的基础之上衍生出来的一颗特殊的二叉树,顾名思义,二叉搜索树主要是用于查找数据的一颗二叉树,其查找的效率很高。二叉搜索树又被称之为二叉排序树,可将存入的数据按照一定的大小次序来排列。

2.二叉搜索树的特性

二叉树的数据中不能存放相同大小的数据,也就是不允许数据的冗余,同时二叉搜索树也是不允许更改数据的,这会影响到二叉搜索树的结构,此外,二叉搜索树的树形结构具有以下特性: 

  • 若其左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若其右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 其左右子树也分别为二叉搜索树
  • 二叉搜索树的中序遍历是按照一定次序来遍历的(从小到大或从大到小)

3.二叉搜索树的模拟实现 

3.1确定二叉搜索树的基本结构

二叉搜索树也是一颗二叉树,具有二叉树的基本结构,也就是左右子节点以及用于存储数据的一个变量,定义具有这三个成员变量的类。

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 BLTree
{
	typedef BSTreeNode<K> Node;
private: 
    Node* _root = nullptr;
public:

}

3.2实现二叉搜索树的基本功能

以下模拟实现的是按照从小到大排序的搜索二叉树。

插入数据

bool Insert(const K& key)//插入值
{
	Node* cur = _root;
	if (_root == nullptr)//根节点为空
	{
		//this->_root->_key
		_root = new Node(key);
		_root->_key = key;
		return true;
	}
	Node* curParent = nullptr;
	while (cur)
	{
		if (cur->_key > key)//根节点大,往左边插入
		{
			curParent = cur;
			cur = cur->_left;

		}
		else if (cur->_key < key)//根节点小,往右边插入
		{
			curParent = cur;
			cur = cur->_right;

		}
		else//插入元素重复,插入失败
		{
			return false;
		}
	}
	cur = new Node(key);
	if (key < curParent->_key)//左插入
	{
		curParent->_left = cur;
	}
	else//右插入
	{
		curParent->_right = cur;
	}
	return true;
}

插入数据首先需要注意是否根节点是否存在,不存在插入的当前数据需要作为根节点,如果根节点存在则依靠搜索二叉树的特性查找插入的位置,通过与当前节点比较数据大小, 大于当前节点数据往左子树查找插入位置,此外,还需要注意需要记录插入节点的上一个节点,当查找到插入位置后再将上一节点指向插入节点(需要判断插入节点为上一节点的左节点还是右节点)。

遍历二叉树(中序遍历)

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

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

需要注意的是这里定义了额外一个函数来封装使用遍历二搜索叉树,这是因为遍历二叉搜索树时需要传递参数--二叉树的根节点,也就是类的成员变量,但是由于我们设置了类成员变量的访问权限为private,所以在类外我们无法使用传递访问类的私有成员。而在类中,成员变量是可以访问的,所以我们使用另一个成员函数来封装调用遍历二叉树的函数。

查询数据

bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)//小于根节点,左子树中查找
		{
			cur = cur->_left;
		}
		else if (cur->_key < key)//大于根节点,右子树中查找
		{
			cur = cur->_right;
		}
		else
		{
			return true;
		}
	}
	return false;//遍历结束未发现
}

二叉搜索树的数据查询也是通过二叉搜索树的特性依次比较当前节点数据大小,依次往下查找对应数据。

删除数据

bool Erase(const K& key)
{
	Node* cur = _root;
	Node* curParent = nullptr;
	while (cur)
	{
		if (cur->_key > key)
		{
			curParent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			curParent = cur;
			cur = cur->_right;
		}
		else//找到待删除元素
		{
			if (cur->_left == nullptr)//待删除元素左子树为空
			{
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else if (curParent->_left == cur)//删除元素为左树
				{
					curParent->_left = cur->_right;
				}
				else删除元素为右树
				{
					curParent->_right = cur->_right;
				}
				delete cur;
			}
			else if (cur->_right == nullptr)//待删除元素右子树为空
			{
				if (cur == _root)
				{
					_root = cur->_left;
				}
				if (curParent->_left == cur)//删除元素为左树
				{
					curParent->_left = cur->_left;
				}
				else删除元素为右树
				{
					curParent->_right = cur->_left;
				}
				delete cur;
			}
			else
			{
				Node* rightMin = cur->_right;
				Node* rightParent = cur;

				while (rightMin->_left)//找到右子树最小元素替代待删除元素
				{
					rightParent = rightMin;
					rightMin = rightMin->_left;
				}
				cur->_key = rightMin->_key;//左树为空

				if (rightParent->_left == rightMin)
					rightParent->_left = rightMin->_right;
				else
					rightParent->_right = rightMin->_right;
				delete rightMin;
			}
			break;
		}
	}
	return false;

}

二叉搜索树的数据删除是较为复杂的,其思想是首先要找到呆删除的数据位置,如果数据不存在,则返回false删除失败,如果待删除的数据被查找到后,需要我们仔细考虑以下几种删除的情况。

  1. 待删除节点的左,右子均为空树
  2. 待删除节点的左子树为空树
  3. 待删除节点的右子树为空树
  4. 待删除节点的左右子树均不为空树

作为第一种情况最为简单,实际上 第二三种的情况包含对情况一的处理,所以实际上要考虑的情况有三种。

1.当待删除节点的左子树为空树:

此时待删除节点的右子树存在,依照二叉搜索树的特性,我们可以直接将待删除节点的右子树与其上一节点连接起来,但是注意需要判断待删除节点是上一节点的左还是右节点。除此之外,我们还需要考虑一种特殊的情况:当待删除数据没有上一节点时,也就是根节点,此时我们只需要将其右节点作为根节点即可

2.当待删除节点的右子树为空树:

这种情况实际上与左树为空是类似的,只是换了一种方向,需要考虑的情况是相同的,这里就不过多介绍。

3.待删除节点的左右子树均不为空树:

这种情况下,左右两颗子树都存在,是最复杂的一种情况, 首要我们要思考的问题是在删除该节点后,确保在不影响二叉树仍然是搜索二叉树的情况下,该如何链接剩余的节点。不难想出,我们需要将待删除节点的左右子树中选取出一个节点去替代删除的节点,重新连接其余节点,那么现在的问题是该选取哪个子节点去替代删除节点。

仔细思考,结合二叉搜索树的特性,其左树节点均小于根节点,右树节点均大于根节点,要确保取代根节点后,仍然保留搜索二叉树的特性,就要求新的根节点要大于左子树的全部节点,小于右子树全部节点,这样我们会发现,右子树的最小节点可以很好的替代删除节点成为新的结点

在找到右子树的最小节点后,可以通过用最小节点的值取代待删除的值,这样待删除的节点就变成右子树最小的节点,在查找右子树最小节点的同时,我们记录其上一节,最后将右子树的最小节点的右子树去链接其上一节点(由于是右子树最小节点,所以其左子树必定为空),达到删除的目的。

4.二叉搜索树的应用

4.1 K模型

K模型实际上是二叉搜索树的模型,在K模型中,二叉搜索树的结构中只存储值Key,Key又叫关键码,可通过传递参数查找该关键码是否存在,由于搜索二叉树的特性,其搜索查找的效率实际上是十分高的。

例如:查找某一个单词是否存在。用词库中所有单词集合中的每个单词作为一个Key值,去构建一棵二叉搜索树,在二又搜索树中检索某一单词是否存在,存在则拼写正确,不存在则拼写错误。

4.2 KV模型 

​​​​​​​KV模型与K模型不同的是,在K模型的基础之上,每个树节点多存储了一个Value值,每一个关键码key,都有与之对应的值Value,即<Key,Value>的键值对。可通过Key值找到与之对应的Value值。

例如:查找某一个单词中文意思。将用词库中所有单词集合中的每个单词作为一个Key值,以及对应的中文翻译,去构建一棵二叉搜索树,通过传递的参数值找到对应关键码Key值中所存有的中文翻译,就到达了查询某一个单词的功能。

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

template <class K, class V>
class BLTree
{
	typedef BSTreeNode<K,V> Node;
private: 
    Node* _root = nullptr;
public:

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值