【二叉树进阶】二叉搜索树

目录

1. 二叉搜索树概念

2. 二叉搜索树的实现

2.1 创建二叉搜索树节点

2.2 创建实现二叉搜索树

2.3 二叉搜索树的查找

2.4 二叉搜索树的插入

2.5 二叉搜索树的删除

2.6 中序遍历

2.7 完整代码加测试

3. 二叉搜索树的应用

3.1 K模型:

3.2 KV模型:

4. 二叉搜索树的性能分析


1. 二叉搜索树概念

二叉搜索树(BST,Binary Search Tree):可以是一颗空树,满足以下性质:

  • 根节点的值大于左子树上所有节点的值,小于右子树上所有节点的值。
  • 它的左子树和右子树也分别为二叉搜索树。

它的中序遍历是有序的:0 1 2 3 4 5 6 7 8 9 ,所以也称为二叉排序树或二叉查找树。

2. 二叉搜索树的实现

int a[ ] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };

2.1 创建二叉搜索树节点

template<class K>
struct BSTreeNode
{
	BSTreeNode* _left;
	BSTreeNode* _right;
	K _key;
	//初始化节点
	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

2.2 创建实现二叉搜索树

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
    //操作实现
    //查找
    bool Find(const K& key) { }
    //插入
    void Insert(const K& key) { }
    //删除
    bool Erase() { }
    //中序遍历
    void InOrder() { }
private:
    Node* _root = nullptr;
}

2.3 二叉搜索树的查找

a、从根开始查找,比根大往右边查找,比根小往左边查找。

b、最多查找高度次,走到空,还没找到,则要查找的值不存在。

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

2.4 二叉搜索树的插入

a、如果树为空,新增节点,赋值给_root指针。

b、按搜索二叉树性质查找插入位置,插入节点。

c、要插入的位置一定为为空的位置,所以要插入,还要找到要插入位置的父节点,让父节点指向新插入的节点。

bool Insert(const K& key)
{
	//如果树为空,新增节点
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	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
		{
			return false;
		}
	}
	cur = new Node(key);
	if (key < parent->_key)
	{
		parent->_left = cur;
	}
	else
	{
		parent->_right = cur;
	}
	return true;
}

2.5 二叉搜索树的删除

a、先判断要删除的元素是否存在,不存在,返回false。

b、若存在,则分为以下情况:

  1. 要删除的节点(叶子节点)左右子树为空
  2. 要删除的节点左子树为空或右子树为空
  3. 要删除的节点左子树右子树都不为空

实际操作起来,情况1和情况2能合并到一起实现,只有两种情况:

  • 要删除的节点的左子树或右子树为空:直接删除-----让父亲指向要删除节点右子树(左子树),再直接删除该节点。

如果要删除的该节点为根,则直接让它的右子树(左子树)赋值给_root让它成为新根即可。

  • 要删除的节点的左右子树都不为空:替换法删除----在它的右子树中找到最小值(最左节点)或在左子树中找到最大值(最右节点),将它的值与要删除节点的值替换,这时就转换为该节点的删除问题。

右子树最左节点的左子树一定为空,左子树最右节点的右子树一定为空,这就转换为第一种情况的删除了。

	bool Erase(const K& key)
	{
		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.要删除节点的左子树为空或者右子树为空
				if (cur->_left == nullptr)
				{
					//如果为根
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					//如果为根且右子树为空
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
				}
				//要删除的左右子树都不为空
				else
				{
					//这里找到是右子树的最左值
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;//要替换的节点
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//替换法交换
					cur->_key = rightMin->_key;
					//转换成删除rightMin
					if (rightMin == rightMinParent->_right)
					{
						//rightMin的左子树一定为空
						rightMinParent->_right = rightMin->_right;
					}
					else
					{
						rightMinParent->_left = rightMin->_right;
					}
					delete rightMin;
				}
				//这里我们去找左子树的最大值
				//else
				//{
				//	Node* leftMaxParent = cur;
				//	Node* leftMax = cur->_left;
				//	while (leftMax->_right)
				//	{
				//		leftMaxParent = leftMax;
				//		leftMax = leftMax->_right;
				//	}
				//	//替换法删除
				//	cur->_key = leftMax->_key;
				//	//转换为删除leftMax
				//	if (leftMax == leftMaxParent->_left)
				//	{
				//		leftMaxParent->_left = leftMax->_left;
				//	}
				//	else
				//	{
				//		leftMaxParent->_right = leftMax->_left;
				//	}
				//	delete leftMax;
				//}
				return true;
			}
		}
		return false;
	}

2.6 中序遍历

中序遍历:按照左子树 根 右子树的方式迭代遍历二叉树。

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

总之,我们在实现二叉搜索树的时候一定要考虑多种情况,每种情况也要多找几个节点进行分析。

2.7 完整代码加测试

BSTree.hpp:

//创建二叉树节点
template<class K>
struct BSTreeNode
{
	BSTreeNode* _left;
	BSTreeNode* _right;
	K _key;
	//初始化节点
	BSTreeNode(const K& key)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};
//创建实现二叉树
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	bool Insert(const K& key)
	{
		//如果树为空,新增节点
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		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
			{
				return false;
			}
		}
		cur = new Node(key);
		if (key < parent->_key)
		{
			parent->_left = cur;
		}
		else
		{
			parent->_right = cur;
		}
		return true;
	}
	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

	bool Erase(const K& key)
	{
		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.要删除节点的左子树为空或者右子树为空
				if (cur->_left == nullptr)
				{
					//如果为根
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					//如果为根且右子树为空
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
				}
				//要删除的左右子树都不为空
				else
				{
					//这里找到是右子树的最左值
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;//要替换的节点
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//替换法交换
					cur->_key = rightMin->_key;
					//转换成删除rightMin
					if (rightMin == rightMinParent->_right)
					{
						//rightMin的左子树一定为空
						rightMinParent->_right = rightMin->_right;
					}
					else
					{
						rightMinParent->_left = rightMin->_right;
					}
					delete rightMin;
				}
				//这里我们去找左子树的最大值
				//else
				//{
				//	Node* leftMaxParent = cur;
				//	Node* leftMax = cur->_left;
				//	while (leftMax->_right)
				//	{
				//		leftMaxParent = leftMax;
				//		leftMax = leftMax->_right;
				//	}
				//	//替换法删除
				//	cur->_key = leftMax->_key;
				//	//转换为删除leftMax
				//	if (leftMax == leftMaxParent->_left)
				//	{
				//		leftMaxParent->_left = leftMax->_left;
				//	}
				//	else
				//	{
				//		leftMaxParent->_right = leftMax->_left;
				//	}
				//	delete leftMax;
				//}
				return true;
			}
		}
		return false;
	}
	bool Find(const K& key)
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		while (cur)
		{
			if (key < cur->_left)
			{
				cur = cur->_left;
			}
			else if (key > cur->_right)
			{
				cur = cur->_right;
			}
			else
			{
				return true;
			}
		}
		return false;
	}
private:
	Node* _root = nullptr;
};
void TestBSTree()
{
	BSTree<int> t;
	int a[] = { 5, 3, 4, 1, 7, 8, 2, 6, 0, 9 };
	for (auto e : a)
	{
		t.Insert(e);
	}
	for (auto e : a)
	{
		t.Erase(e);
		t.InOrder();
	}
}

Test.cpp:

#include"BSTree.hpp"
int main()
{
	TestBSTree();
	return 0;
}

运行结果:

3. 二叉搜索树的应用

3.1 K模型

K模型只有Key作为关键码,结构中只需要存储Key即可,关键码就是要搜索的值。

用途:判断Key在不在。

比如:给一个单词,判断该单词是否正确:

  • 以词库中所有单词集合中的每个单词作为key,构建一颗二叉搜索树
  • 在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

其实就是上面我们实现的二叉搜索树的查找。这里不在重复实现了。

3.2 KV模型

每一个关键码Key,都有与子对应的Value,即<Key,Value>的键值对。

用途:通过Key找对应的Value。

  • 比如:英汉词典就是英文与中文的对应关系,通过英文可以快速找到对应中文,英文单词与其中文构成<world,chinese>就构成一种键值对;
  • 比如:统计单词次数,统计成功后,给定单词可以快速找到其出现次数,单词与其出现次数<world,count>就构成一种键值对。

也很简单,其实就是让二叉搜索树中的节点多存一个值Value,查找、插入、删除等操作依旧是按照key去实现的。

改造二叉搜索树为KV结构模型:

template<class K,class V>
struct BSTreeNode
{
	BSTreeNode<K,V>* _left;
	BSTreeNode<K,V>* _right;
	K _key;
	V _value;//多存一个值value
	BSTreeNode(const K& key,const V& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{}
};
template<class K,class V>
class BSTree
{
	typedef BSTreeNode<K,V> Node;
public:
	bool Insert(const K& key,const V& value)
	{ }
	void _InOrder(Node* root)
	{ }
	void InOrder()
	{ }
	bool Erase(const K& key)
	{ }
	Node* Find(const K& key)//返回节点的指针,这里不再实现
	{ }
private:
	Node* _root = nullptr;
};

应用测试1:输入英文查找对应的中文

void TestBSTree()
{
	BSTree<string, string> dict;
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("int", "整型");
	dict.Insert("search", "查找");
	dict.Insert("insert", "插入");
	string str;
	while (cin >> str)
	{
		BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret == nullptr)
		{
			cout << "词库中无此单词" << endl;
		}
		cout << ret->_value << endl;
	}
}

结果:

应用测试2:查找水果出现的次数

void TestBSTree()
{
	string arr[] = { "苹果","香蕉","西瓜","西瓜","香梨","西瓜" ,"苹果" ,"西瓜" ,"西瓜" ,"香蕉" ,"苹果" };
	BSTree<string, int> t;
	int i = 0;
	for (auto e : arr)
	{
		BSTreeNode<string, int>* ret = t.Find(e);
		//ret为空说明要查找的是第一次出现
		if (ret == nullptr)
		{
			t.Insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	t.InOrder();
}

结果:

3.3 二叉搜索树的修改

二叉搜索树中Key是不能被修改的。

如果是KV模型的搜索树,可以修改Value,但是不能修Key

4. 二叉搜索树的性能分析

插入、删除操作都必须先查找,所有查找效率代表了二叉搜索树中各个操作的性能。

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同(无序、接近有序),可能得到不同结构的二叉搜索树:

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log2N(以2为底N的对数)。
  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:N。
如果为单支树,二叉搜索树的性能就没有了,那么这种情况就要用到AVL树和红黑树了,后续再讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值