[C++][数据结构]二叉搜索树:介绍和实现

二叉搜索树

概念

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

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

模拟实现(非递归)

节点

	template<class K, class V>
	struct BSTreeNode	//节点
	{
		using Node = BSTreeNode<K, V>;
		Node* _left;	//左节点
		Node* _right;	//右节点
		K _key;			//键
		V _value;		//值

		BSTreeNode(const K& key, const V& value)
			:_left(nullptr)
			, _right(nullptr)
			, _key(key)
			,_value(value)
		{}				//拷贝构造
	};

寻找

根据BSTree的特点,

  • 比该节点的值大,就走到该节点右节点
  • 比该节点的值小,就走到该节点左节点

插入

  1. 先寻找,有相同值则return false
  2. 插入,链接父节点和原来的子树

删除

没有孩子/一个孩子

对于这种情况,

  • 当删除节点左边为空,就将该节点右边托付给父亲
  • 右边为空,就将左边托付给父亲
    所以对于删除叶子节点也可以归纳到这里面

两个孩子:替换法

找一个能替换删除节点的节点,交换值,转换删除替换节点
比如:删除图中3这个节点
我们可以将左子树的最大节点或者右子树的最左节点替换掉3
即左子树最右节点或者右子树最左节点

在这里插入图片描述

rightMin的问题

假如rightMinParent为空指针,那下面这句特殊情况会出错

rightMinParent->_left = rightMin->_right;

在这里插入图片描述

这幅图中,当删除根节点时,右子树最左节点为空,不进循环,rightMinParent为空,访问空节点的左子树会出现错误

代码

测试代码

void TestBSTree()
{
	BSTree<string, string> dict;
	dict.Insert("insert", "插入");
	dict.Insert("erase", "删除");
	dict.Insert("left", "左边");
	dict.Insert("string", "字符串");

	string str;
	while (cin >> str)
	{
		auto ret = dict.Find(str);
		if (ret)
		{
			cout << str << ":" << ret->_value << endl;
		}
		else
		{
			cout << "单词拼写错误" << endl;
		}
	}

	string strs[] = { "苹果", "西瓜", "苹果", "樱桃", "苹果", "樱桃", "苹果", "樱桃", "苹果" };
	// 统计水果出现的次
	BSTree<string, int> countTree;
	for (auto str : strs)
	{
		auto ret = countTree.Find(str);
		if (ret == NULL)
		{
			countTree.Insert(str, 1);
		}
		else
		{
			ret->_value++;
		}
	}
	countTree.InOrder();
}

完整代码

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode	//节点
	{
		using Node = BSTreeNode<K, V>;
		Node* _left;	//左节点
		Node* _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
	{
		using Node = BSTreeNode<K, V>;
	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 != nullptr)
			{
				if (cur->_key > key)
				{
					parent = cur;
					cur = cur->_left;
				}
				else if (cur->_key < key)
				{
					parent = cur;
					cur = cur->_right;
				}
				else
				{
					return false;
				}
			}

			//查找完再判断是父节点的左树还是右数
			//标记为A
			cur = new Node(key, value);
			if (parent->_key > key)
			{
				parent->_right = cur;
			}
			else
			{
				parent->_right = cur;
			}
			return true;
		}

		//查找,并返回这个节点的指针(位置)
		Node* Find(const K& key)
		{
			Node* cur = _root;
			while (cur != nullptr)
			{
				if (cur->_key > key)
				{
					cur = cur->_right;
				}
				else if (cur->_key < key)
				{
					cur = cur->_left;
				}
				else
				{
					return cur;
				}
			}
			return nullptr;
		}

		//删除节点
		bool Erase(const K& key)
		{
			//并没有在开头判断删除节点是否为根节点
			//而是在下面
			Node* parent = nullptr;
			Node* cur = _root;
			while (cur != nullptr)
			{
				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)//对于删除根节点进行特判
						{
							_root = cur->_right;
						}
						else
						{	//功能A
							if (cur == parent->_right)
							{
								parent->_right = cur->_right;
							}
							else
							{
								parent->_left = cur->_right;
							}
						}
						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == _root)
						{
							_root = cur->left;
						}
						else
						{	//功能A
							if (cur == parent->_right)
							{
								parent->_right = cur->_left;
							}
							else
							{
								parent->_left = cur->_left;
							}
						}
						delete cur;
						return true;
					}
					else
					{
						// 采用右树最左节点替换法
						Node* rightMin = cur->_right;
						Node* rightMinParent = cur;
						while (rightMin->_left)
						{
							rightMinParent = rightMin;
							rightMin = rightMin->_left;
						}
						cur->_key = rightMin->_key;
						if (rightMin == rightMinParent->_right)
						{
							rightMinParent->_right = rightMin->_right;
						}
						else
						{
							rightMinParent->_left = rightMin->_left;
						}
						delete rightMin;
						return true;
					}
				}
			}
			return false;
		}

		void InOrder()//中序打印
		{
			_InOrder(_root);
		}//因为外部取不到_root
		//所以再套一层

	private:
		Node* _root = nullptr;

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

结语

现在再来写BSTree感觉也不是很困难,希望对你有帮助,我们下次见
(补充一下,代码是不全的,BSTree() 和~BSTree()都没写,用的默认的)

上一篇:堆排序以及TOP-K问题- 向下调整算法有一个前提:左右子树必须是堆


下一篇:[Qt的学习日常]--信号和槽