数据结构进阶——搜索二叉树

数据结构进阶——二叉搜索树

  • 1. 二叉搜索树的概念和定义
  • 2. 二叉搜索树的操作
    • 2.1 二叉搜索树的插入
    • 2.2 二叉搜索树的查找
    • 2.3 二叉搜索树的删除
  • 3. 二叉搜索树的实际应用
    • 3.1 两种模型加二叉搜索树完整代码
    • 3.2 KV模型练习
  • 4. 二叉搜索树性能分析


1. 二叉搜索树的概念和定义


1. 二叉搜索树又称二叉排序树,它可以是一棵空树,或者是具有以下性质的二叉树:

  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值;
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值;
  • 它的左右子树也分别为二叉搜索树。

在这里插入图片描述

2. 二叉搜索树的定义也非常简单,就是普通的二叉树定义方法:

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 BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// ... 相关接口实现
protected:
	Node* _root = nullptr;
};
  • 节点的定义中提供了一个构造函数,方便在后续实现相关接口的时候使用。

2. 二叉搜索树的操作


2.1 二叉搜索树的插入


1. 非递归写法:

在这里插入图片描述

  • 思路很简单,就是拿要插入的值从根节点开始比较,如果比根节点的值大就继续跟右子树比较;如果比根节点值小就继续跟左节点比较,直到找到空节点,在空节点的位置插入即可。
  • 如果根节点就是空节点,说明当前的树是空树,直接在根节点插入即可。
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)	// 直到当前节点为空,说明要在此插入
		{
			parent = cur;

			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				// 默认搜索二叉树中,不能出现重复元素
				return false;
			}
		}
		
		cur = new Node(key);
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
		else
		{
			parent->_left = cur;
		}
		return true;
	}

protected:
	Node* _root = nullptr;
};
  • cur指针指向当前节点,parent指针指向当前节点的父节点。这里要注意,找到插入位置后,直接把新节点赋值给cur是不能完成插入的,因为单单这样只是让cur指向了新节点,无法将新节点和这棵树链接起来。必须借助parent指针,记录要插入位置的父节点,然后让parent的孩子指针指向新节点。

2. 递归写法:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 递归插入
	bool _InsertR(Node*& root, const K& key) // 这里引用设计的非常巧妙
	{
		if (root == nullptr)
		{
			root = new Node(key);
			return true;
		}

		if (root->_key < key)
			return _InsertR(root->_right, key);
		else if (root->_key > key)
			return _InsertR(root->_left, key);
		else
			return false;
	}

	bool InsertR(const K& key)
	{
		return _InsertR(_root, key);
	}

protected:
	Node* _root = nullptr;
};
  • C++中,经常使用一个_开头的内部函数,和不带_的外部函数来设计递归。因为如果我们直接在外部使用_InsertR,我们会找不到根节点。解决方案就是设计一个InsertR,让InsertR去调用_InsertR,这样就可以找到根节点了。
  • 这里我用一张图来解释引用的巧妙之处:
    在这里插入图片描述
  • 假设我们要在这棵树中插入一个11,调用_InsertR函数接口,root是引用,所以root就是_root。发现11比10大,递归调用右子树插入。此时将root->_right传给了下一个递归函数的root,也是传引用,所以此时的root就是根节点的_right,发现root为空,直接创建新节点,然后赋值给root,完成链接。

3. 中序遍历,验证是否成功建树:

  • 不难发现,如果对一颗搜索树进行中序遍历,那么得到的将是一个升序序列,这个规律可以直接拿来使用,亦可以用来验证搜索树是否正确建立。
template<class K>
class BSTree
{
	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);
	}

protected:
	Node* _root = nullptr;
};

2.2 二叉搜索树的查找


1. 思路:

  • 从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找;
  • 最多查找高度次;
  • 走到到空,还没找到,这个值不存在。

2. 非递归实现(循环实现):

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 查找
	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; // 找不到,返回假
	}

protected:
	Node* _root = nullptr;
};

3. 递归实现:

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 递归查找
	bool _FindR(Node* root, const K& key)
	{
		// 走到了空,说明没找到
		if (root == nullptr)
		{
			return false;
		}
		
		if (root->_key < key)
			return _FindR(root->_right, key);
		else if (root->_key > key)
			return _FindR(root->_left, key);
		else
			return true; // 找到了,返回真
	}
	
	bool FindR(const K& key)
	{
		return _FindR(_root, key);
	}
	
protected:
	Node* _root = nullptr;
};

2.3 二叉搜索树的删除


在这里插入图片描述

// 用下面数组中的数依次插入,即可得到上图的树
int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

1. 思路:

  • 首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:

    • a.要删除的结点无孩子结点(如1,4,7,13这种叶子结点);
    • b.要删除的结点只有左孩子结点(如14);
    • c.要删除的结点只有右孩子结点(如10);
    • d.要删除的结点有左、右孩子结点(如8,3,6)。
  • 看起来有待删除节点有4中情况,实际a情况可以与b或c情况合并起来,因此真正的删除过程如下:

    • 情况b:删除该结点,且使被删除节点的双亲结点指向被删除节点的左孩子结点——直接删除;
    • 情况c:删除该结点,且使被删除节点的双亲结点指向被删除结点的右孩子结点——直接删除;
    • 情况d:在它的右子树中寻找最左节点(最小节点,中序遍历的第一个节点),用它的值填补到要被删除节点中,再删除最左节点;或在它的左子树,寻找最右节点(最大节点),用它的值填补到要被删除节点中,再删除最右节点——替换法删除。

2. 非递归实现:

  • 情况a可以归类到情况b和c中,因为左右节点都为空,是左右节点其中有一个为空的子集条件;
  • 最左节点不一定是parent的左孩子,例如删除节点8,右子树的最左节点是10,是8这个parent的右节点。
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 删除
	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		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) // 左节点为空的情况
				{
					if (cur == _root)
					{
						// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
						// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
						_root = cur->_right;
						delete cur;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
							delete cur;
						}
						else
						{
							parent->_right = cur->_right;
							delete cur;
						}
					}
				}
				else if (cur->_right == nullptr) // 右节点为空的情况
				{
					if (cur == _root)
					{
						// 还是根节点特殊处理
						_root = cur->_left;
						delete cur;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
							delete cur;
						}
						else
						{
							parent->_right = cur->_left;
							delete cur;
						}
					}
				}
				else
				{
					// 左右节点都不为空
					Node* parent = cur;	// 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
					Node* subLeft = cur->_right;	// 右树的最小节点(最左节点)
					while (subLeft->_left)
					{
						parent = subLeft;
						subLeft = subLeft->_left;
					}

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

					// 判断最左节点是parent的左节点还是右节点
					if (parent->_left == subLeft)
					{
						parent->_left = subLeft->_right;
						delete subLeft;
					}
					else
					{
						parent->_right = subLeft->_right;
						delete subLeft;
					}
				}

				return true;
			}
		}
		return false;
	}
	
protected:
	Node* _root = nullptr;
};

3. 递归实现:

  • 刚开始还是找点,如果key < root->_key,就递归删除左子树;如果key > root->_key就递归删除右子树;
  • 找到了要删除的点,开始分情况处理:
    • b,c情况直接删除;
    • d情况要先找到右子树的最左节点,然后交换最左节点和要删除节点的_key。要注意,交换后,以root位置(刚才要删除节点的位置)为根节点的右子树仍然是一颗搜索二叉树(这是一个规律,也很好推,可以直接拿来用),所以可以转换成子问题,递归走右子树的删除。
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	// 递归删除
	bool _EraseR(Node*& root, const K& key)
	{
		if (root == nullptr)
			return false;

		if (root->_key < key)
		{
			return _EraseR(root->_right, key);
		}
		else if (root->_key > key)
		{
			return _EraseR(root->_left, key);
		}
		else
		{
			// 找到了,开始删除
			if (root->_left == nullptr)
			{
				Node* del = root;
				root = root->_right;
				delete del;
				return true;
			}
			else if (root->_right == nullptr)
			{
				Node* del = root;
				root = root->_left;
				delete del;
				return true;
			}
			else
			{
				// 左右节点都不为空
				Node* subLeft = root->_right;
				while (subLeft->_left)
				{
					subLeft = subLeft->_left;
				}

				swap(subLeft->_key, root->_key);

				// 转换成在右子树去递归删除
				return _EraseR(root->_right, key);
			}
		}
	}

	bool EraseR(const K& key)
	{
		return _EraseR(_root, key);
	}
	
protected:
	Node* _root = nullptr;
};

4. 测试:
在这里插入图片描述

void Test()
{
	int a[] = { 8, 3, 1, 10, 6, 4, 7, 14, 13 };
	BSTree<int> bt;
	for (auto e : a)
	{
		bt.InsertR(e);
	}
	bt.InOrder();
	cout << endl;
	
	// 测试普通删除
	for (auto e : a)
	{
		bt.Erase(e);
	}


	for (auto e : a)
	{
		bt.Insert(e);
	}
	bt.InOrder();
	cout << endl;

	// 测试递归删除
	for (auto e : a)
	{
		bt.EraseR(e);
	}
}

这段代码如果能跑通,说明基本没有问题,因为这里一旦报错大概率就是崩溃。


3. 二叉搜索树的实际应用


3.1 两种模型加二叉搜索树完整代码


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

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

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

  • 该种方式在现实生活中非常常见:
    • 比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;
    • 再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。

3. 两棵树的完整代码如下:

  • key_value相比key模型,更改了一些细节。
    • 如二叉树节点的设计,多了一个_value
    • 查找函数中,如果找到了对应节点,就返回该节点,找不到返回空指针;
    • 还对中序遍历做了一些修改,使其可以同时打印键和值。
// key模型
namespace key
{
	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 BSTree
	{
		typedef BSTreeNode<K> Node;
	public:
		// 默认构造
		BSTree() = default; // C++11后新增的语法
		// 或
		/*BSTree()
		{}*/

		// 循环插入
		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			Node* parent = nullptr;
			Node* cur = _root; // 这里不能用引用
			while (cur)	// 直到当前节点为空,说明要在此插入
			{
				parent = cur;

				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					// 默认搜索二叉树中,不能出现重复元素
					return false;
				}
			}

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

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

		void InOrder()
		{
			_InOrder(_root);
		}

		// 查找
		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 _FindR(Node* root, const K& key)
		{
			// 走到了空,说明没找到
			if (root == nullptr)
			{
				return false;
			}

			if (root->_key < key)
				return _FindR(root->_right, key);
			else if (root->_key > key)
				return _FindR(root->_left, key);
			else
				return true; // 找到了,返回真
		}

		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		// 递归插入
		bool _InsertR(Node*& root, const K& key) // 这里引用设计的非常巧妙
		{
			if (root == nullptr)
			{
				root = new Node(key);
				return true;
			}

			if (root->_key < key)
				return _InsertR(root->_right, key);
			else if (root->_key > key)
				return _InsertR(root->_left, key);
			else
				return false;
		}

		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}


		// 删除
		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			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) // 左节点为空的情况
					{
						if (cur == _root)
						{
							// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
							// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
							_root = cur->_right;
							delete cur;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
								delete cur;
							}
							else
							{
								parent->_right = cur->_right;
								delete cur;
							}
						}
					}
					else if (cur->_right == nullptr) // 右节点为空的情况
					{
						if (cur == _root)
						{
							// 还是根节点特殊处理
							_root = cur->_left;
							delete cur;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
								delete cur;
							}
							else
							{
								parent->_right = cur->_left;
								delete cur;
							}
						}
					}
					else
					{
						// 左右节点都不为空
						Node* parent = cur;	// 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
						Node* subLeft = cur->_right;	// 右树的最小节点(最左节点)
						while (subLeft->_left)
						{
							parent = subLeft;
							subLeft = subLeft->_left;
						}

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

						if (parent->_left == subLeft)
						{
							parent->_left = subLeft->_right;
							delete subLeft;
						}
						else
						{
							parent->_right = subLeft->_right;
							delete subLeft;
						}
					}

					return true;
				}
			}
			return false;
		}

		// 递归删除
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				if (root->_left == nullptr)
				{
					Node* del = root;
					root = root->_right;
					delete del;
					return true;
				}
				else if (root->_right == nullptr)
				{
					Node* del = root;
					root = root->_left;
					delete del;
					return true;
				}
				else
				{
					// 左右节点都不为空
					Node* subLeft = root->_right;
					while (subLeft->_left)
					{
						subLeft = subLeft->_left;
					}

					swap(subLeft->_key, root->_key);

					// 转换成在右子树去递归删除
					return _EraseR(root->_right, key);
				}
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

		// 后序遍历,销毁树
		void Destroy(Node*& root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}

		// 析构
		~BSTree()
		{
			Destroy(_root);
		}

		// 拷贝构造函数
		BSTree(const BSTree<K>& t)
		{
			_root = Copy(t._root);
		}

		// 先序拷贝
		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);

			return newRoot;
		}

		BSTree<K>& operator=(BSTree<K> t)
		{
			swap(_root, t._root);
			return *this;
		}

	protected:
		Node* _root = nullptr;
	};
}

/*--------------------------------------------------------------------------*/

// key_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:
		// 默认构造
		BSTree() = default; // C++11后新增的语法
		// 或
		/*
		BSTree()
		{}
		*/

		// 循环插入
		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)	// 直到当前节点为空,说明要在此插入
			{
				parent = cur;

				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					// 默认搜索二叉树中,不能出现重复元素
					return false;
				}
			}

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

		// 中序遍历
		void _InOrder(Node* root)
		{
			if (root == nullptr)
				return;
			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}

		void InOrder()
		{
			_InOrder(_root);
		}

		// 查找
		Node* 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 cur; // 找到了,返回该节点
				}
			}

			return nullptr; // 找不到,返回空指针
		}

		// 递归查找
		Node* _FindR(Node* root, const K& key)
		{
			// 走到了空,说明没找到
			if (root == nullptr)
			{
				return nullptr;
			}

			if (root->_key < key)
				return _FindR(root->_right, key);
			else if (root->_key > key)
				return _FindR(root->_left, key);
			else
				return root; // 找到了,返回该节点
		}

		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}

		// 递归插入
		bool _InsertR(Node*& root, const K& key, const V& value) // 这里引用设计的非常巧妙
		{
			if (root == nullptr)
			{
				root = new Node(key, value);
				return true;
			}

			if (root->_key < key)
				return _InsertR(root->_right, key);
			else if (root->_key > key)
				return _InsertR(root->_left, key);
			else
				return false;
		}

		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}


		// 删除
		bool Erase(const K& key)
		{
			Node* parent = nullptr;
			Node* cur = _root;
			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) // 左节点为空的情况
					{
						if (cur == _root)
						{
							// 若要删除的节点是根节点,要特殊处理,直接把根给右子树即可
							// 因为此时parent==nullptr,不单独处理会发生空指针的解引用
							_root = cur->_right;
							delete cur;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_right;
								delete cur;
							}
							else
							{
								parent->_right = cur->_right;
								delete cur;
							}
						}
					}
					else if (cur->_right == nullptr) // 右节点为空的情况
					{
						if (cur == _root)
						{
							// 还是根节点特殊处理
							_root = cur->_left;
							delete cur;
						}
						else
						{
							if (cur == parent->_left)
							{
								parent->_left = cur->_left;
								delete cur;
							}
							else
							{
								parent->_right = cur->_left;
								delete cur;
							}
						}
					}
					else
					{
						// 左右节点都不为空
						Node* parent = cur;	// 这里不要置空,置空在while循环进不去时,会在判断是parent左孩子还是右孩子时解引用空指针
						Node* subLeft = cur->_right;	// 右树的最小节点(最左节点)
						while (subLeft->_left)
						{
							parent = subLeft;
							subLeft = subLeft->_left;
						}

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

						if (parent->_left == subLeft)
						{
							parent->_left = subLeft->_right;
							delete subLeft;
						}
						else
						{
							parent->_right = subLeft->_right;
							delete subLeft;
						}
					}

					return true;
				}
			}
			return false;
		}

		// 递归删除
		bool _EraseR(Node*& root, const K& key)
		{
			if (root == nullptr)
				return false;

			if (root->_key < key)
			{
				return _EraseR(root->_right, key);
			}
			else if (root->_key > key)
			{
				return _EraseR(root->_left, key);
			}
			else
			{
				if (root->_left == nullptr)
				{
					Node* del = root;
					root = root->_right;
					delete del;
					return true;
				}
				else if (root->_right == nullptr)
				{
					Node* del = root;
					root = root->_left;
					delete del;
					return true;
				}
				else
				{
					// 左右节点都不为空
					Node* subLeft = root->_right;
					while (subLeft->_left)
					{
						subLeft = subLeft->_left;
					}

					swap(subLeft->_key, root->_key);

					// 转换成在右子树去递归删除
					return _EraseR(root->_right, key);
				}
			}
		}

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}

		// 后序遍历,销毁树
		void Destroy(Node*& root)
		{
			if (root == nullptr)
				return;
			Destroy(root->_left);
			Destroy(root->_right);
			delete root;
			root = nullptr;
		}

		// 析构
		~BSTree()
		{
			Destroy(_root);
		}

		// 拷贝构造函数
		BSTree(const BSTree<K, V>& t)
		{
			_root = Copy(t._root);
		}

		// 先序拷贝
		Node* Copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newRoot = new Node(root->_key, root->_value);
			newRoot->_left = Copy(root->_left);
			newRoot->_right = Copy(root->_right);

			return newRoot;
		}

		BSTree<K, V>& operator=(BSTree<K, V> t)
		{
			swap(_root, t._root);
			return *this;
		}

	protected:
		Node* _root = nullptr;
	};
}

3.2 KV模型练习


1. 输入单词,查找对应的中文翻译:

void findWord()
{
	// 输入单词,查找单词对应的中文翻译
	key_value::BSTree<string, string> dict;
	dict.Insert("string", "字符串");
	dict.Insert("tree", "树");
	dict.Insert("left", "左边、剩余");
	dict.Insert("right", "右边");
	dict.Insert("sort", "排序");
	dict.InOrder();
	// 插入词库中所有单词
	string str;
	while (cin >> str)
	{
		key_value::BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret == nullptr)
		{
			cout << "单词拼写错误,词库中没有这个单词:" << str << endl;
		}
		else
		{
			cout << str << "中文翻译:" << ret->_value << endl;
		}
	}
}

2. 统计水果出现的次数:

void fruitCount()
{
	// 统计水果出现的次数
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
   "苹果", "香蕉", "苹果", "香蕉" };

	key_value::BSTree<string, int> countTree;
	for (auto e : arr)
	{
		key_value::BSTreeNode<string, int>* ret = countTree.Find(e);
		if (ret == nullptr)
		{
			countTree.Insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}

	countTree.InOrder();
}

4. 二叉搜索树性能分析


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

  • 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

  • 但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
    在这里插入图片描述

  • 最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N

  • 最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N

2. 问题:

  • 如果退化成单支树,二叉搜索树的性能会非常低效。那能否进行改进,不论按照什么次序插入关键码,二叉搜索树的性能都能达到最优?
  • 我们后面还会学习AVL树和红黑树,他们就是性能优秀的搜索二叉树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/626793.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

航电AFDX板卡,标准PMC规格、高性能ARINC664网络接口板卡

是一款标准PMC规格、高性能ARINC664网络接口板卡。该产品支持ARINC664网络仿真、测试及数据分析等应用需求&#xff1b;支持2个ARINC664端口&#xff0c;采用RJ45插件形式&#xff0c;每个端口高达64K缓存&#xff0c;2个端口可作为2个独立端口&#xff0c;也可作为冗余端口使用…

10G UDP协议栈 IP层设计-(5)IP RX模块

一、模块功能 1、解析目的IP是否是本节点的源IP&#xff0c;如果是则进行如下的处理&#xff0c;如果不是则无需上上级传递 2、提取MAC层发送过来的IP报文&#xff0c;并提取其中的数据字段&#xff08;上层协议字段&#xff09;&#xff0c;传递给上级 3、提取IP报文头中的…

Android Studio无法使用Google翻译问题记录

背景 其实关于Google翻译不能用的问题已经出现很久了&#xff0c;之前Google关掉了很多国内的一些Google服务&#xff0c;但是Google翻译还是能用的&#xff0c;直到不知什么时候起&#xff0c;Google翻译也不能用呢。 每次换电脑安装完AS后第一件事就是下载插件 Settings-Pl…

[ardunio ide导入blinker库]

1 blinker库下载地址 https://github.com/blinker-iot/blinker-library2 导入方法一 zip导入 项目 -> 导入库 ->添加.zip库 3 导入方法二

排序(一)----冒泡排序,插入排序

前言 今天讲一些简单的排序,冒泡排序和插入排序,但是这两个排序时间复杂度较大,只是起到一定的学习作用,只需要了解并会使用就行,本文章是以升序为例子来介绍的 一冒泡排序 思路 冒泡排序是一种简单的排序算法&#xff0c;它重复地遍历要排序的序列&#xff0c;每次比较相邻…

【web网页开发制作】Html+Css+Js游戏主题特效及轮播效果网页作业天涯明月刀(7页面附源码)

HTMLCSSJS游戏主题轮播效果 &#x1f354;涉及知识&#x1f964;写在前面✨特效展示特效1、轮播幻灯效果特效2和3、鼠标悬浮及点击效果 &#x1f367;一、网页主题&#x1f333;二、网页效果Page1、首页Page2、游戏简介Page3、新闻中心Page4、互动专区Page5、视听盛宴Page6、用…

基于MSWA相继加权平均的交通流量分配算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于MSWA相继加权平均的交通流量分配算法matlab仿真.如图所示交通网络中&#xff0c;包含6个节点、11各路段、9个OD对。经枚举可得每个OD对间存在3条无折返有效路…

助力数字农林业发展服务香榧智慧种植,基于YOLOv5全系列【n/s/m/l/x】参数模型开发构建香榧种植场景下香榧果实检测识别系统

作为一个生在北方但在南方居住多年的人&#xff0c;居然头一次听过香榧&#xff08;fei&#xff09;这种作物&#xff0c;而且这个字还不会念&#xff0c;查了以后才知道读音&#xff08;fei&#xff09;&#xff0c;三声&#xff0c;这着实引起了我的好奇心&#xff0c;我相信…

“ModuleNotFoundError: No module named ‘selenium‘”报错如何解决

接上节&#xff1a;测试平台开发之测试框架改造并发执行及结果隔离(1) 上节博客的末尾提到&#xff1a;在命令窗口执行python main.py 可是执行的时候遇到了如下报错&#xff1a; ERRORS _____________________________________________________________ ERROR collecting te…

讲解SSM的xml文件

概述&#xff1a;这些配置文件很烦&#xff0c;建议直接复制粘贴 springMVC.xml文件 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XM…

Spring AI项目配置1.0.0-SNAPSHOT快照版指导

Spring AI项目配置1.0.0-SNAPSHOT快照版开发指导 说明pom文件修改 说明 请在更换Spring AI的全程中&#xff0c;科学上网&#xff0c;因为国内镜像和maven官方仓库还没有Spring AI的依赖&#xff0c;需要的依赖目前存放在https://repo.spring.io如果你使用的maven是自己配置&a…

Flutter 依据JSON数据自动生成实体类

json自动化生成工具 点击这里可以跳转 页面是这样的 然后在左边输入你的json数据&#xff0c;它会自动生成对应的实体类 生成的实体类是如下&#xff1a; import package:json_annotation/json_annotation.dart; part merch_region.g.dart;JsonSerializable()class MerchReg…

贷款中介CRM管理系统解决方案

一、贷款中介行业背景介绍 随着贷款中介行业的快速发展&#xff0c;贷款中介业务逐渐成为企业和个人融资的重要渠道。然而&#xff0c;贷款中介行业存在信息不对称、风险控制不力等难题。给金融稳定带来潜在风险。 二、方案目的和意义 鑫鹿贷款中介系统解决方案旨在规范贷款中…

JavaScript基础(七)

isNaN //用来判断一个变量是不是一个非数字 不是来判断是不是number类型&#xff0c;而是判断当前值能不能转为number类型&#xff0c;OK&#xff1f;懂了。 还有同学不明白&#xff0c;来看实例: <script> //isNaN(非数字)→true &#xff08;数字&#xff09;→fal…

文献速递:多模态深度学习在医疗中的应用--多模式婴儿脑分割技术:模糊引导深度学习

Title 题目 Multimodal Infant Brain Segmentation by Fuzzy-informed Deep Learning 多模式婴儿脑分割技术&#xff1a;模糊引导深度学习 01 文献速递介绍 日益普及的非侵入式婴儿脑磁共振图像&#xff08;MRI&#xff09;为准确理解脑主要发展轨迹的动态性提供了机会&…

10,hadoop优化与LZO压缩

hadoop多目录与LZO压缩 1.HDFS存储多目录 存储多目录&#xff1a; namenode&#xff0c; datanode namenode: 可以在当前节点中创建几个 namenode的多目录&#xff0c; 就是 虽说可以是多个目录&#xff0c;但是这个namenode多目录中&#xff0c;内容都是一样&#xff0c; 就…

使用System.Drawing绘制基本几何图形

1.使用System.Drawing绘制一个正方形 using System; using System.Drawing; using System.Windows.Forms;public partial class MyForm : Form {public MyForm(){// 你可以在这里设置Form的双缓冲&#xff0c;以避免绘制时出现的闪烁 this.DoubleBuffered true;}protected o…

基于django医用耗材网上申领系统的实现

基于django医用耗材网上申领系统的实现 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 管理员登录 系统在安全性的验证方面究竟做了什么功能呢&#xff1f;在做之前我们也进行了思量&…

全面提升数据采集效率:亮数据产品的应用与评估详解

全面提升数据采集效率&#xff1a;亮数据产品的应用与评估详解 文章目录 全面提升数据采集效率&#xff1a;亮数据产品的应用与评估详解背景应用场景&#xff1a;平台首页信息抓取准备评测素材详细的产品使用和评测流程产品介绍亮数据的IP代理服务亮数据的爬虫工具及采集技术 注…

一个视频AI自动抠像 速度快 操作简单 - RobustVideoMattingGU

RVM的GUI版本&#xff1a; 一款基于Robust Video Matting&#xff08;RVM&#xff09;源码的图形用户界面&#xff08;GUI&#xff09;版本&#xff0c;采用先进的pyqt6框架和qdarkstyle风格设计&#xff0c;为视频编辑爱好者和二次创作者打造了一个功能丰富的工具箱。这款软件…