二叉搜索树及其基本操作

本文介绍了二叉搜索树的概念、节点结构、查找、插入、遍历、销毁、删除等基本操作。二叉搜索树在查找、插入和删除操作中具有较高的效率,但数据有序或接近有序时可能退化为单支树。文章还讨论了二叉搜索树在K模型和KV模型中的应用。

二叉搜索树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
(1)若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
(2)若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
(3)它的左右子树也分别为二叉搜索树
对二叉搜索树进行中序遍历可以得到一个有序序列。
二叉搜索树例子
一个二叉搜索树例子 int a [] = {5,3,4,1,7,8,2,6,0,9};

二叉树搜索树的节点结构

二叉搜索树的节点内有三个变量,分别是指向左右两个孩子节点的指针和其自身的值域。

template<class T>
struct  BSTNode
{
	BSTNode(const T& val= T())
		: left(nullptr)
		, right(nullptr)
		, data(val)
	 {}
	BSTNode<T>* left;
	BSTNode<T>* right;
	T data;
};

二叉搜索树的查找

若根节点不为空:
1.如果根节点的值等于查找的值,返回根节点的地址。
2.如果根节点的值大于查找的值,则在其左子树中查找。
3.如果跟姐点的值小于,则在其右子树中查找。
否则,返会空指针。

Node* Find(const T& data)  //查找
	{
		Node* cur = root;
		while (cur)
		{
			if (data == cur->data)
				return cur;
			else if (data < cur->data)
				cur = cur->left;
			else
				cur = cur->right;
		}
		return nullptr;
	}

二叉搜索树的插入

(1)若树为空,则直接插入,即让根节点直接指向待插入节点。
(2)若树不为空,按照二叉树搜索树性质查找插入位置,并保存其父节点位置,然后插入新节点。
在学习时,我发现实现的算法,一般都是在叶子节点的位置插入,而去插入的值不会与二叉搜索树中值重复。因此,我在想要是遇到插入的位置是非叶子节点或者插入重复值这种情况应该怎么处理。在网上查找资料发现,1二叉树是一种动态查找表。特点是,树的结构不是一次生成的,而是在查找过程中,当树中不存在关键字等于给定值的结点时再进行插入。新插入的结点一定是一个新添加的叶子结点,并且是查找不成功时查找路径上访问的最后一个结点的左孩子或右孩子结点。
以下是插入一个值域为10节点的例子。
在这里插入图片描述

bool Insert(const T& data) //插入
	{
		//插入的树是空树
		if (nullptr == root)
		{
			root = new Node(data);
			return true;
		}
		//非空
		//找待插入元素在树中的位置,并保存其双亲
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			parent = cur;
			if (data < cur->data)
				cur = cur->left;
			else if (data > cur->data)
				cur = cur->right;
			else
				return false;
		}
		//插入新节点
		cur = new Node(data);
		if (data < parent->data)
			parent->left = cur;
		else
			parent->right = cur;
		return true;
		
	}

二叉搜索树的遍历

对二叉搜索树进行中序遍历可以获得一个有序序列。中序遍历的思路为:左子树 —> 根结点 —> 右子树。

	void _InOrder(Node* pRoot)
	{
		if (pRoot)
		{
			_InOrder(pRoot->left);
			std::cout << pRoot->data << " ";
			_InOrder(pRoot->right);
		}
	}

二叉搜索树的销毁

销毁思路:先将左子树销毁,再将右子树销毁,最后释放根节点,再将指向根节点的指针置空。

	void _DestroyBSTree(Node*& pRoot)
	{
		if (pRoot)
		{
			_DestroyBSTree(pRoot->left);
			_DestroyBSTree(pRoot->right);
			delete pRoot;
			pRoot = nullptr;
		}
	}

二叉搜索树节点的删除

首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题
a与c合并起来,在二叉搜索树中,又分为三种情况
在这里插入图片描述
b的三种情况与a和c的类似。

在这里插入图片描述

d有两种情况。处理思路:
1.不直接删除cur,而是在cur的子树中找一个替代节点。
左子树:找左子树中最大(最右侧)的节点
右子树:找右子树中最小(最左侧)的节点
注意:替代节点在cur的左子树或右子树中查找均可,一般情况下从右子树中查找替代节点
2.将替代节点中的值域赋值给待删除节点
3.将替代节点删除
在这里插入图片描述

	bool Erase(const T& data)
	{
		//判断树是否为空
		if (nullptr == root)
			return false;
		//1.找到待删除节点再树中的位置,并记录其父节点。
		Node* cur = root;
		Node* parent = nullptr;
		while (cur)
		{
			if (data == cur->data)
				break;
			else if (data < cur->data)
			{
				parent = cur;
				cur = cur->left;
			}
			else if (data > cur->data)
			{
				parent = cur;
				cur = cur->right;
			}
		}
		//如果树中没有待删除对应的节点
		if (nullptr == cur)
			return false;
		//找到待删除节点的位置-->进行删除
		if (nullptr == cur->left)
		{
			//cur只有右孩子或者cur是叶子节点
			if (nullptr == parent)
				//cur一定是根节点
				root = cur->right;
			//delete cur;
			else {
				if (cur == parent->left)
					parent->left = cur->right;
				else
					parent->right = cur->right;
		    //delete cur;
			}
		}
		else if(nullptr == cur->right)
		{
			//cur只有左孩子
			if (nullptr == parent)
			{
				root = cur->left;
				//delete cur;
			}
			else {
				if (cur == parent->left)
					parent->left = cur->left;
				else
					parent->right = cur->left;
                //delete cur;
			}
		}
		else
		{
			//cur左右孩子都有
		    //1.在cur的子树中找一个替代节点--左右子树中查找均可
			//一般情况下是在左右子树中查找的
			//左子树中查找时一定时最大的节点,也就是最右侧节点
			//右子树中查找时一定时最下的节点,也就是最左侧节点
			Node* del = cur->right;
			//parent = cur;
			
			while (del->left)
			{
				parent = del;
				del = del->left;
			}
			//2.将替代节点中的值域赋值给待删除节点
			cur->data = del->data;
			//3.删除替代节点
			if (del == parent->left)
				parent->left = del->right;
			else
				parent->right = del - right;
			cur = del;
		}
		delete cur;
		return true;
	}

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的
深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log2N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2

二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下,这个问题在AVL树和红黑树中得到了较好的解决。

二叉搜索树的应用

1. K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
·以单词集合中的每个单词作为key,构建一棵二叉搜索树
·在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。
2. KV模型:每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。
该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
·<单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key
·查询英文单词时,只需给出英文单词,就可快速找到与其对应的key

以上内容的完整代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值