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


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树和红黑树,他们就是性能优秀的搜索二叉树。

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
1. 什么是二叉树二叉树是一种树形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉树通常用于搜索和排序。 2. 二叉树的遍历方法有哪些? 二叉树的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子树,最后访问右子树。中序遍历是从根节点开始遍历,先访问左子树,再访问根节点,最后访问右子树。后序遍历是从根节点开始遍历,先访问左子树,再访问右子树,最后访问根节点。 3. 二叉树的查找方法有哪些? 二叉树的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子树中查找;如果要查找的值比当前节点大,则继续在右子树中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉树的插入与删除操作如何实现? 二叉树的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子树中插入;如果大于当前节点的值,则继续在右子树中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子树中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉树? 平衡二叉树是一种特殊的二叉树,它保证左右子树的高度差不超过1。这种平衡可以确保二叉树的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉树包括红黑树和AVL树。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

-指短琴长-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值