【Test】 二叉搜索树

1. 二叉搜索树的概念

🐧① 二叉搜索树(BST,Binary Search Tree),也称二叉排序树或者二叉查找树;

🍎② 左子树的值都 小于 根结点,右子树的值都 大于根结点;

✌③ 一颗搜索树如下所示:
在这里插入图片描述

2. 二叉搜索树 K 模型的代码实现

2.1 Find ( ) 查找的实现

🐧① 从根结点开始查找🔍,key 比根结点大则往根结点右边走,比根结点小则往根结点的左边走;

🐧② 最多查高度次

// 二叉树的查找
bool Find(const K& key)
{
	if (_root == nullptr)
	{
		return false;
	}

	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;
}
2.2 Insert () 插入的实现

🐧① 树为空,直接在根结点插入数据;
🐧② 树不为空,则先查找要插入结点的位置,然后再插入数据;
🐧③ 我们需要用一个指针来记录被插入结点位置的 父结点
在这里插入图片描述

	// 插入的实现
bool Insert(const K& key)
{
	// 1.可能刚开始的时候是一颗 空树 的情况
	if (_root == nullptr)
	{
		// 这是 new 的特性:new对自定义类型自动调用构造函数并完成初始化
		_root = new Node(key);
		return true;
	}

	Node* cur = _root;
	Node* parent = nullptr;

	while (cur)
	{
		if (cur->_key > key)
		{
			parent = cur;
			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			parent = cur;
			cur = cur->_right;
		}
		else
		{
			// 二叉搜索树一般不允许数据冗余的情况
			return false;
		}
	}

	cur = new Node(key);

	if (parent->_key > key)
	{
		parent->_left = cur;
	}
	else if (parent->_key < key)
	{
		parent->_right = cur;
	}

	return true;
}

2.3 InOrder ( ) 中序遍历的实现

🐧① 因为中序遍历要用到 递归 实现,因为类外面的测试函数要用到 BSTree 这个类的 _root 私有的成员变量,在类外面无法获取,所以我们该怎么获取呢?

a. 将其设置为友元函数(不可取,因为这只是一个测试函数,不能随意的将测试函数作为友元);

b. 采取缺省函数,给默认值( 不可取) , 原因如下:

-root非静态成员变量

在这里插入图片描述

c.将递归函数嵌套定义,将 _InOrder定义成私有的成员函数就好啦🐧

在这里插入图片描述

2.4 Erase ( ) 删除的实现

二叉搜索树删除的 细节比较多,可大致分成以下三种情况;

🐧① 要删除的结点只有左孩子结点,在删除结点的时候,我们也要考虑被删除的结点是 父亲的左孩子还是右孩子
注意:左右孩子都为空的情况可以归纳为这一类哦❗
还要特殊考虑当前删除的结点是根结点哦❗


🍎同理:要删除的结点 只有右孩子结点 ,这种情况类似,所以就不讲述了。
在这里插入图片描述


在这里插入图片描述


🐧② 如果要删除的结点有左右孩子结点

采取替换法,用待删除结点的左子树最大结点或者右子树的最小结点来替换待删除的结点;
细节:
🍎Ⅰ、找到了右子树的最小结点,然后将它与待删除的结点替换之后,然后删除rightMin,但是删除之前一定要考虑该删除的结点是否还有孩子

🍎Ⅱ、要记得考虑右子树的最小结点是其父结点的左孩子还是右孩子;(为什么要考虑这个情况呢? — 因为假如我们要删除的结点时根结点,此时右子树的最小结点的父结点可能为根结点,造成了 rightMin是父结点rightMinParent右孩子的特殊情况)。


下面这两张图极其重要,可以完美的解释这段代码:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


	// 二叉搜索树的删除
	bool Erase(const K& key)
	{  
		if (_root == nullptr)
			return false;

		Node* cur = _root;
		Node* parent = cur;

		// 1. 首先找到要删除的结点
		while (cur)
		{
			if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
			else
			{
				// ① 如果要删除的结点没有 右孩子
				if (cur->_right == nullptr)
				{
					// 如果要删除的结点是根结点
					if (parent == cur)
					{
						_root = cur->_left;	
					}
					else
					{

						if (parent->_key > key)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
					delete cur;
				}
				// ② 如果要删除的结点没有 左孩子
				else if (cur->_left == nullptr)
				{
					// 如果要删除的结点是根结点
					if (parent == cur)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_key > key)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
					delete cur;
				}
				// ③ 如果要删除的结点有 左右孩子结点
				else
				{
					// 先找到右子树的最小结点

					Node* rightMin = cur->_right;
					Node* rightMinParent = cur;

					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}

					swap(cur->_key, rightMin->_key);

					// 判断 rightMin 在 rightMinParent的左边还是右边
					if (rightMinParent->_left == rightMin)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;

					delete rightMin;

				}
				return true;
			}
		}
		return false;
	}

3. 二叉搜索树的 KV 模型

🐧① K模型:K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。

比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以词库中所有单词集合中的每个单词作为key,构建一棵二叉搜索树
在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。


🐧②KV模型:每一个关键码 key,都有与之对应的值 Value 即<Key, Value>的键值对。

统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对;

  • 以下是改造成 KV 模型的代码
    🍎 如何改造呢 ? ------ 其实只需要在 K模型 的基础上面稍加改动即可;
    🍎 Node* Find(const K& key)函数的编写,返回找到的结点的指针,这个时候就不是返回bool值了;因为要涉及对 value的修改操作,所以必须返回指针。
namespace key_value {

	template<class K, class V>
	struct BSTreeNode {

		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;
		K _key;
		V _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)
		{

			if (_root == nullptr)
			{
				_root = new Node(key, value);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}

			cur = new Node(key, value);
			if (parent->_key > key)
			{
				parent->_left = cur;
			}
			else if (parent->_key < key)
			{
				parent->_right = cur;
			}

			return true;
		}


		// 查找
		Node* Find(const K& key)
		{
			if (_root == nullptr)
				return nullptr;

			Node* cur = _root;
			while (cur)
			{
				if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

		// 删除
		bool Erase(const K& key)
		{
			// 1.先找到要删除的结点
			Node* parent = nullptr;
			Node* cur = _root;

			while (cur)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					// 如果要删除的结点左孩子为空
					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;
							}
						}
						delete cur;
					}
					// 左右孩子都不为空的情况
					// 替换法:寻找右子树的最小结点来替换
					else
					{
						Node* rightMinParent = cur;
						Node* rightMin = cur->_right;

						// 找到右子树的最小结点
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}

						swap(cur->_key, rightMin->_key);

						// 最左节点不一定是叶子节点

						if (rightMin == rightMinParent->_right)
						{
							rightMinParent->_right = rightMin->_right;
						}
						else
						{
							rightMinParent->_left = rightMin->_right;
						}

						delete rightMin;
					}
					return true;
				}
			}
			return false;
		}


		// 中序遍历
		void Inorder()
		{
			_Inorder(_root);
		}



	private:

		void _Inorder(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			_Inorder(root->_left);
			cout << root->_key << " " << root->_value;
			_Inorder(root->_right);
		}

		Node* _root = nullptr;
	};

	// 查找是否有某种水果
	void TestBSTreeKeyValue()
	{
		BSTree<string, string> tr2;

		tr2.Insert("apple", "苹果");
		tr2.Insert("orange", "橘子");
		tr2.Insert("pear", "梨");
		tr2.Insert("strawberry", "草莓");

		string str;
		while (cin >> str)
		{
			BSTreeNode<string, string>* ptr = tr2.Find(str);

			if (ptr != nullptr)
			{
				cout << ptr->_key << " " << ptr->_value << endl;
			}
			else
			{
				cout << "Sorry,查无此水果,请重新输入!" << endl;
			}
		}
	}

	// 统计某种水果的数量
	void TestBSTreeCount()
	{
		string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" };

		BSTree<string, int> countTree;

		for (auto str : arr)
		{
			BSTreeNode<string, int>* ptr = countTree.Find(str);

			if (ptr == nullptr)
			{
				countTree.Insert(str, 1);
			}
			else
			{
				ptr->_value++;
			}
		}

		countTree.Inorder();
	}
}

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

最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
在这里插入图片描述

🍎为了解决上面这个最差情况下的二叉搜索树问题,所以引出了AVL树和红黑树,后面的博客我将会谈到!

在这里插入图片描述

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,这是一个比较具体的问题,我可以给出一个可能的解法。 首先我们需要定义一个二叉搜索树的节点类: ```python class Node: def __init__(self, value): self.value = value self.left = None self.right = None ``` 接下来我们可以编写一个函数来构建二叉搜索树。这个函数需要接受一个列表作为输入,然后将列表中的元素插入到二叉搜索树中: ```python def build_bst(nums): if not nums: return None root = Node(nums[0]) for num in nums[1:]: insert(root, num) return root def insert(root, value): if root is None: return Node(value) if value < root.value: root.left = insert(root.left, value) else: root.right = insert(root.right, value) return root ``` 接下来我们需要将这个二叉搜索树转换成双向链表。我们可以定义一个函数来进行中序遍历,并在遍历的过程中进行节点之间的连接: ```python def bst_to_dll(root): if root is None: return None head, tail = inorder_traversal(root) return head def inorder_traversal(root): if root is None: return None, None left_head, left_tail = inorder_traversal(root.left) right_head, right_tail = inorder_traversal(root.right) node = Node(root.value) if left_head is not None: left_tail.right = node node.left = left_tail else: left_head = node if right_head is not None: right_head.left = node node.right = right_head else: right_tail = node return left_head, right_tail ``` 最后我们可以编写一个函数来进行测试。这个函数需要构建一个二叉搜索树,然后将它转换成双向链表,并分别打印二叉搜索树、遍历结果和双向链表: ```python def test(): nums = [5, 3, 7, 2, 4, 6, 8] root = build_bst(nums) print("Binary Search Tree:") print_tree(root) print("Inorder Traversal:") print_list(inorder_traversal(root)[0]) print("Doubly Linked List (Forward):") head = bst_to_dll(root) while head is not None: print(head.value, end=" ") head = head.right print() print("Doubly Linked List (Backward):") tail = inorder_traversal(root)[1] while tail is not None: print(tail.value, end=" ") tail = tail.left print() def print_tree(root): if root is None: return print(root.value) print_tree(root.left) print_tree(root.right) def print_list(head): while head is not None: print(head.value, end=" ") head = head.right print() ``` 这样,我们就可以通过 `test()` 函数来进行测试了。注意,这个算法的时间复杂度是 $O(n)$,其中 $n$ 是二叉搜索树中节点的个数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未来可期LJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值