搜索二叉树

1.搜索二叉树的概念

搜索二叉树也称二叉排序树和二叉查找树,它具有以下性质:

若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
它的左右子树也分别为二叉搜索树。
如上图所示就是一棵二叉搜索树,很显然,它是一棵特殊的二叉树。
我们不难发现,当二叉搜索树用中序遍历的时候,它是从小到大依次排序的,例如,我们中序遍历上图这棵树的结果是:1,3,4,6,7,8,10,13,14。所以说,这个结构的效率还是很高的,况且,遍历一遍自动排序,也是很方便的。

2.搜索二叉树的实现

搜索二叉树无非也是一棵二叉树,所以它只有一些特殊的功能和普通二叉树不一样,我们实现主要它的增、删、查,因为搜索二叉树左节点要比这个节点小,右节点的值要比它大,所以是不能插入已经存在的值的结点,插入的值也是不能修改的。下面我们开始实现。

2.1插入

要插入结点,我们首先需要构建一个结点Node的结构体,里面存放左节点,右节点和要插入的key值,

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

	}
	typedef BSTreeNode<K> node;
	node* _left;
	node* _right;
	K _key;

};
下面我们开始实现BSTree类主体。
template<class K>
class BinarySearchTree
{
	typedef BSTreeNode<K> node;
public:
private:
	node* _root = nullptr;
};

开始插入结点:

要插入这个值,我们首先要去找这个值应该插入的位置在哪里,如果这个值比根节点的值大,就应该去根节点的右子树找,如果小就去左子树找,如果要插入的值,与当前根节点值相等,那就证明已经存在当前的值,那不能插入,需要返回false。

bool Insert(const K& key)
{
    node* cur = _root;
    while (cur)
  {
	if (cur->_key < key)
	{
		cur = cur->_right;//去右子树找
	}
	else if (cur->_key > key)
	{
		cur = cur->_left;//去左子树找,
	}
	else
	{
		return false;//要插入值已经存在,返回false。
	}
    
  }
//....cur为空,要插入的值不存在,可以插入
}

代码运行到省略号这里,代表可以插入值,但是我们需要首先判断根节点_root是不是空结点,如果是空结点,我们访问空结点的左右子树是野指针的

bool Insert(const K& key)
{
	if (_root == nullptr)//判断根为空的情况
	{
		_root = new node(key);
		return true;
	}
	node* cur = _root;
	while (cur)
	{
		if (cur->_key < key)
		{
			cur = cur->_right;//去右子树找
		}
		else if (cur->_key > key)
		{
			cur = cur->_left;//去左子树找,
		}
		else
		{
			return false;//要插入值已经存在,返回false。
		}

	}
	//....cur为空,要插入的值不存在,可以插入
}

这个时候,我们就可以直接在省略号的位置,直接new一个值为key的结点。

cur = new node(key);
return true;

大家想一下,这样就结束了吗?很显然没有,因为这个cur是一个局部变量,我们只是new出了一个结点赋给了cur,但是并没有把它链接在这棵树上,所以,我们在找要插入的位置的时候,需要一个父节点,每次cur变成新的结点的之前,cur都需要先赋给parent。再者,我们最后查好的时候,需要判断cur到底插入在parent结点的左边还是右边即:

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new node(key);
		return true;
	}
	node* cur = _root;
	node* parent = nullptr;
	while (cur)
	{
		if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else
		{
			return false;
		}

	}
	cur = new node(key);
	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	return true;
}

这才是一个完整的插入,我们写一个中序遍历,插入几个结点,验证一下结果。

2.2中序遍历

void _Inorder(node* root)
{
	if (root == nullptr)
	{
		return;
	}
	_Inorder(root->_left);
	cout << root->_key << " ";
	_Inorder(root->_right);
}
void Inorder()
{
	_Inorder(_root);
	cout << endl;
}

因为中序需要递归,而函数的参数默认有this->_root,所以我们不需要传参,但是递归需要传参,所以在这里我们封装一个_Inorder(Node* root),真正的中序函数,直接调用就可以啦。

测试:

#include"BSTree.h"
void test1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BinarySearchTree<int> b;
	for (auto e : a)
	{
		b.Insert(e);
	}
	b.Inorder();
}
int main()
{
	test1();
	return 0;
}

结果与预期相符

2.3查找

查找其实我们插入一开始就写的过,就是看key比根节点大还是小,大就去根的右边,小就去左边,相等就是找到了。

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

2.4删除

删除算是搜索二叉树里面难度相对比较高的,很多细节我们需要考虑。

首先,我们考虑,删除的结点如果没有孩子,也就是删除叶子结点,还是比较容易的,直接把这个结点删除就可以了;如果删除的结点有一个孩子,也就是左右子树有一个为空,其实相对也还能实现,我们只需让自己的父节点指向自己自己的孩子就可以了。就比如如果要删除当前节点,但是当前节点的左子树为空,那就可以直接让父节点指向自己的右子树,然后删除当前节点就可以了;但是还有一种情况,如要删除的结点左右子树都不为空怎么办?父节点最多只能指向一个,现在我有两个结点,该怎么删?这里介绍一种方法——替换法。即要删除的结点左右孩子都不为空,我们不能破坏搜索树的规则,那就去找到左子树的最大值,或者我们找到右子树的最小值,找到其中一个,然后与要删除的值替换,然后再删除我们要删除的值。因为我们去找左子树的最大值,首先它要比要删除结点cur的其它左子树都要大,比cur右子树的所有结点都要小,况且它身为cur左子树的最大值,它断然是没有右子树的,那把这个结点的值赋给cur,我们都把左最大的值给要删除的cur了,然后删除左最大这个结点不就Ok了嘛;同理我们也可以找cur右子树最小值,它会比所有cue的右子树结点都小,比所有cur左子树结点都大,它身为cur右树最小值结点,肯定是没有左子树的。那这个左树最大结点,右树最小结点怎么找呢?右树最小结点,一定在右子树最左边,因为它的最小的,它一定是别人的左子树,所以它一直都在左边,直到它这里没有左子树了,它就是最小的。同理,左子树最大值也是左子树最右边的结点。

下面我们慢慢来实现,这里是以找右树最小结点为例

bool Erase(const K& key)
{
	node* cur = _root;
	node* parent = nullptr;
	while (cur)
	{
		if (cur->_key < key)//比根结点值大
		{
			parent = cur;
			cur = cur->_right;//去右子树找
		}
		else if (cur->_key > key)//比根结点值小
		{
			parent = cur;
			cur = cur->_left;//去左子树找
		}
		else//与根节点值相等,找到要删除的结点了
		{
			if (cur->_left == nullptr)//cur左边为空,把右孩子交给父节点
			{
				if (cur == _root)//左子树为空,且要删根节点情况,直接让右孩子为根
				{
					_root = cur->_right;
				}
				else
				{
					if (parent->_left == cur)//
					{
						parent->_left = cur->_right;
					}
					else
					{
						parent->_right = cur->_right;
					}
				}

				delete cur;
				return true;
			}
			else if (cur->_right == nullptr)//cur右树为空
			{
				if (cur == _root)//判断右树为空,删除_root情况
				{
					_root = cur->_left;
				}
				else
				{
					if (parent->_left == cur)
					{
						parent->_left = cur->_left;
					}
					else
					{
						parent->_right = cur->_left;
					}
				}

				delete cur;
				return true;
			}
			else//替换法:处理要删除的结点左右孩子都不为空
			{
				node* RightParent = cur;
				node* Rightmin = cur->_right;
				while (Rightmin->_left)//去右树左边找右树最小结点
				{
					RightParent = Rightmin;
					Rightmin = Rightmin->_left;
				}
				cur->_key = Rightmin->_key;//右树最小结点的值赋给要删除的cur,删除右树最小结点
				if (RightParent->_left == Rightmin)
					RightParent->_left = Rightmin->_right;
				else
					RightParent->_right = Rightmin->_right;
				delete Rightmin;
				return true;
			}

		}

	}
	return false;
}

我们测试结果

void test1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BinarySearchTree<int> b;
	for (auto e : a)
	{
		b.Insert(e);
	}
	b.Inorder();
	b.Erase(8);//删除根节点8
	b.Inorder();
}
int main()
{
	test1();
	return 0;
}

再把所有结点都删一遍

void test1()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BinarySearchTree<int> b;
	for (auto e : a)
	{
		b.Insert(e);
	}
	b.Inorder();
	b.Erase(8);//删除根节点8
	b.Inorder();

	cout << "********************" << endl;
	for (auto e : a)
	{
		b.Erase(e);
		b.Inorder();
	}
}

验证可以看到,我们的删除并没有问题。

这次分享就到这里,下面附上源码

3.源码

//面向对象面向君,不负代码不负卿
#include<iostream>
using namespace std;
template<class K>
struct BSTreeNode
{
	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{

	}
	typedef BSTreeNode<K> node;
	node* _left;
	node* _right;
	K _key;

};
template<class K>
class BinarySearchTree
{
	typedef BSTreeNode<K> node;
public:
	void _Inorder(node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_Inorder(root->_left);
		cout << root->_key << " ";
		_Inorder(root->_right);
	}
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}
	bool Find(const K& key)
	{
		node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
	bool Insert(const K& key)
	{
		if (_root == nullptr)//判断根为空的情况
		{
			_root = new node(key);
			return true;
		}
		node* cur = _root;
		node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;//去右子树找
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;//去左子树找,
			}
			else
			{
				return false;//要插入值已经存在,返回false。
			}

		}
		//....cur为空,要插入的值不存在,可以插入
		cur = new node(key);
		if (parent->_key > key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	bool Erase(const K& key)
	{
		node* cur = _root;
		node* parent = nullptr;
		while (cur)
		{
			if (cur->_key < key)//比根结点值大
			{
				parent = cur;
				cur = cur->_right;//去右子树找
			}
			else if (cur->_key > key)//比根结点值小
			{
				parent = cur;
				cur = cur->_left;//去左子树找
			}
			else//与根节点值相等,找到要删除的结点了
			{
				if (cur->_left == nullptr)//cur左边为空,把右孩子交给父节点
				{
					if (cur == _root)//左子树为空,且要删根节点情况,直接让右孩子为根
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_left == cur)//
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}

					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)//cur右树为空
				{
					if (cur == _root)//判断右树为空,删除_root情况
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_left == cur)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}

					delete cur;
					return true;
				}
				else//替换法:处理要删除的结点左右孩子都不为空
				{
					node* RightParent = cur;
					node* Rightmin = cur->_right;
					while (Rightmin->_left)//去右树左边找右树最小结点
					{
						RightParent = Rightmin;
						Rightmin = Rightmin->_left;
					}
					cur->_key = Rightmin->_key;//右树最小结点的值赋给要删除的cur,删除右树最小结点
					if (RightParent->_left == Rightmin)
						RightParent->_left = Rightmin->_right;
					else
						RightParent->_right = Rightmin->_right;
					delete Rightmin;
					return true;
				}

			}

		}
		return false;
	}

private:
	node* _root = nullptr;
};

  • 33
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值