C++ 二叉搜索树BinarySearchTree

目录

一.概念

二.分部模拟实现(K模型)

1.二叉树结点

2.二叉搜索树构建

3.查找(非递归)

4.插入(非递归)

5.删除(非递归)

6.查找(递归)

 7.插入(递归)

8.删除(递归)

三.模拟实现总代码(K模型)

四.模拟实现(KV模型)

五.二叉搜索树的应用

1.K模型

2.KV模型

六.二叉搜索树的性能


前言:二叉搜索树是为学习map和set做的铺垫,了解了二叉搜索树的特性,有助于更好的理解map和set。

一.概念

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

① 若它的左子树不为空,则左子树上所有结点的值都小于根结点的值

② 若它的右子树不为空,则右子树上所有结点的值都大于根结点的值

③ 它的左右子树也分别为二叉搜索树

二.分部模拟实现(K模型)

1.二叉树结点

        _key为该二叉树值,_left为左结点,_right为右结点。

template<class K>
struct BSTreeNode
{
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;

	K _key;

	BSTreeNode(const K& key)
		: _left(nullptr)
		, _right(nullptr)
		, _key(key)
	{}
};

2.二叉搜索树构建

        实现二叉搜索树的构造、拷贝构造、赋值重载、析构函数。

        拷贝构造是一定要实现的,通过子函数CopyTree通过前序遍历进行依次构造结点,而构造函数用编译器默认生成的就够了,但是因为实现了拷贝构造,编译器就不会生成默认的构造,因此使用C++11的关键字default来强制编译器生成构造。

        赋值重载是现代写法,析构函数也是通过子函数DestoryTree,通过递归依次释放结点。

template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
private:
	void DestroyTree(Node* root)
	{
		if (root == nullptr)
		{
			return;
		}

		DestroyTree(root->_left);
		DestroyTree(root->_right);
		delete root;
	}

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

		Node* copyNode = new Node(root->_key);
		CopyTree(root->_left);
		CopyTree(root->_right);

		return copyNode;
	}
public:
	// C++11,强制让编译器自己生成构造
	BSTree() = default;

	BSTree(const BSTree<K>& t)
	{
		_root = CopyTree(t._root);
	}

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

	~BSTree()
	{
		DestroyTree(_root);
		_root = nullptr;
	}
private:
	Node* _root = nullptr;
};

3.查找(非递归)

        遍历寻找,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走。

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;
}

4.插入(非递归)

        找到位置就插入进去。如果是空树,直接插入作为根结点。

        需要定义一个parent变量,这样在找到插入的位置后,可以让该插入结点的父节点parent链接上该新插入的结点。

        前面与查找类似,如果要查找的key值大于该结点的key值,就往该结点的右子树走,如果要查找的key值小于该结点的key值,就往该结点的左子树走,如果相等了,就不能插入了,直接返回false。

        找到要插入的位置之后,new出要插入的新结点,然后判断这个结点的值是大于parent还是小于parent,如果大于就放在右边,如果小于就放在左边。

bool Insert(const K& key)
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}

	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
		{
			return false;
		}
	}

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

	return true;
}

5.删除(非递归)

        删除需要考虑的比较多。

        首先也是需要定义一个parent变量,这样删除掉结点后,可以让其parent连接到删除结点的孩子结点。

        接着也是与查找类似,在找到之后,就要分情况讨论。

        第一种情况是要删除的结点的左孩子为空:这种情况首先要注意要删除的结点是根结点的情况,因为是根结点,所以parent是空,因此要单独写,让新的根结点变为原根结点的右结点。另外就是正常情况,要判断删除的这个结点的值是parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让其parent的左孩子结点链接上该结点的右孩子结点,如果是右孩子结点,就让parent的右孩子结点也链接上该结点的右孩子结点。

        第二种情况是要删除的结点的右孩子为空:这种情况与第一种情况类似,也是要注意特殊情况,但是这种情况是让新的根结点变为原根结点的左结点。然后再判断删除的这个结点时parent的左孩子结点还是右孩子结点,如果是左孩子结点,就让parent的左孩子结点链接上该结点的左孩子结点,如果是右孩子结点,就让parent的右孩子结点链接上该结点的左孩子结点。

        第三种情况是要删除的结点的左右孩子都不为空:出现这种情况时,不能直接删除掉需要删除的结点,而是需要替代删除,保证二叉搜索树的特性。这种替代有两种方法(第一种是用该结点的左子树的最大结点替代,第二种是用该结点的右子树的最小结点替代),这里我们采用第二种方法。首先需要定义两个变量,一个是用来找到这个去替代的结点minRight,另一个是该替代的结点的父节点minParent,这里要注意定义minParent时,要让其等于cur,如果让其等于nullptr,那么如果该结点的左结点没有结点了就会导致minParent一直为空,出现野指针问题。定义了之后,通过while循环找到该结点右子树的最小结点(即该结点右子树的最左的结点),接下来可以交换要删除结点的值和替代结点的值,也可以直接赋值,因为替代了结点后就被删除,是否交换并不重要。接下来再判断替代结点是minParent的左孩子结点还是右孩子结点,如果是左孩子结点,就让minParent的左孩子节点链接上替代结点的右孩子节点,如果是右孩子结点,就让minParent的右孩子结点链接上替代结点的右孩子结点。

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)
				{
					_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* minParent = cur;
				Node* minRight = cur->_right;
				while (minRight->_left)
				{
					minParent = minRight;
					minRight = minRight->_left;
				}

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

				if (minParent->_left == minRight)
				{
					minParent->_left = minRight->_right;
				}
				else
				{
					minParent->_right = minRight->_right;
				}

				delete minRight;
			}

			return true;
		}
	}

	return false;
}

6.查找(递归)

        递归写法就采用子函数的方法,查找、插入、删除都是如此。

         如果要查找的值比此结点大,就递归调用该结点的右子树结点,如果要查找的值比此结点小,就递归调用该结点的左子树结点,如果相等了就返回true,遇到空结点就返回false。

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

private:
	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;
		}
	}

 7.插入(递归)

        与递归查找法类似,只是插入是遇到空会创建插入结点,返回true,相等时返回false。

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

private:
	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;
		}
	}

8.删除(递归)

        前面就是查找,找到之后,也是要分三种情况讨论。

        这里我们注意形参root的变量类型是Node*&,是指针的引用。

        第一种左为空:因为是指针引用,因此直接让root等于root的右子树结点,因为是引用,所以如果上一个递归调用的是root->_right那么这个root就是上一个root->_right的别名,因此就相当于上一个的root->_right = 当前的root->_right,这样就相当于删除掉了这个root。

        第二种右为空:与第一种类似,root = root->_left,把_right改为_left即可。

        第三种左右都不为空:这个就只需要定义一个替代变量minRight即可,然后通过while找到这个变量,这个一定要交换,不能赋值更改,因为接下来是要使用递归的。要注意这里递归的是root的右子树结点(不能是当前结点,原因是在交换之后,该结点已经不满足二叉搜索树的特性了,用该结点就会出错),其右子树结点及其右子树所有结点都是满足二叉搜索树的特性的,这样递归之后,就满足上面两种情况之一,就可以完成删除了。

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

private:
	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
		{
			Node* del = root;
			// 删除
			// 左为空
			if (root->_left == nullptr)
			{
				root = root->_right;
			}
			// 右为空
			else if (root->_right == nullptr)
			{
				root = root->_left;
			}
			// 都不为空
			else
			{
				Node* minRight = root->_right;
				while (minRight->_left)
				{
					minRight = minRight->_left;
				}

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

				return _EraseR(root->_right, key);
			}

			delete del;
			return true;
		}
	}

三.模拟实现总代码(K模型)

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;
	private:
		void DestroyTree(Node* root)
		{
			if (root == nullptr)
			{
				return;
			}

			DestroyTree(root->_left);
			DestroyTree(root->_right);
			delete root;
		}

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

			Node* copyNode = new Node(root->_key);
			CopyTree(root->_left);
			CopyTree(root->_right);

			return copyNode;
		}
	public:
		// C++11,强制让编译器自己生成构造
		BSTree() = default;

		BSTree(const BSTree<K>& t)
		{
			_root = CopyTree(t._root);
		}

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

		~BSTree()
		{
			DestroyTree(_root);
			_root = nullptr;
		}

		bool Insert(const K& key)
		{
			if (_root == nullptr)
			{
				_root = new Node(key);
				return true;
			}

			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
				{
					return false;
				}
			}

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

			return true;
		}

		// const Node* Find(const K& key)
		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 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)
						{
							_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* minParent = cur;
						Node* minRight = cur->_right;
						while (minRight->_left)
						{
							minParent = minRight;
							minRight = minRight->_left;
						}

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

						if (minParent->_left == minRight)
						{
							minParent->_left = minRight->_right;
						}
						else
						{
							minParent->_right = minRight->_right;
						}

						delete minRight;
					}

					return true;
				}
			}

			return false;
		}

		// 递归写法:
		bool FindR(const K& key)
		{
			return _FindR(_root, key);
		}

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

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

		void InOrder()
		{
			_InOrder(_root);
		}
	private:
		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 _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 _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
			{
				Node* del = root;
				// 删除
				// 左为空
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				// 右为空
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				// 都不为空
				else
				{
					Node* minRight = root->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}

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

					return _EraseR(root->_right, key);
				}

				delete del;
				return true;
			}
		}

		Node* _root = nullptr;

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

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

四.模拟实现(KV模型)

        这个和K模型的很类似,就大致实现一下,这里用修改的是递归版本的。

        只需要增加一下value变量,增加一下模板,注意key加const。

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		BSTreeNode<K, V>* _left;
		BSTreeNode<K, V>* _right;

		const 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:

		void InOrder()
		{
			_InOrder(_root);
			cout << endl;
		}
		Node* FindR(const K& key)
		{
			return _FindR(_root, key);
		}

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

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

	private:
		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
			{
				Node* del = root;
				// 删除
				if (root->_left == nullptr)
				{
					root = root->_right;
				}
				else if (root->_right == nullptr)
				{
					root = root->_left;
				}
				else
				{
					Node* minRight = root->_right;
					while (minRight->_left)
					{
						minRight = minRight->_left;
					}

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

					return _EraseR(root->_right, key);
				}

				delete del;
				return true;
			}
		}

		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, value);
			else if (root->_key > key)
				return _InsertR(root->_left, key, value);
			else
				return false;
		}

		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;
			}
		}

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

			_InOrder(root->_left);
			cout << root->_key << ":" << root->_value << endl;
			_InOrder(root->_right);
		}
	private:
		Node* _root = nullptr;
	};
}

五.二叉搜索树的应用

1.K模型

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

例子:判断单词是否拼写正确

        用词库中的单词集合中的每个单词作为key,构建二叉搜索树。

2.KV模型

        每一个关键码key,都有与之对应的值value,即<Key, Value>的键值对

例子:①英汉词典,中英文的对应关系, 通过英文可以快速找到对应的中文。

           ②统计单词的次数,单词与其出现次数构成键值对。

六.二叉搜索树的性能

对于相同的元素,如果key插入的次序不同,可能会得到不同结果的二叉搜索树。

        这里要注意二叉搜索树的效率是O(N),而不是O(log2N)  。

        最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其比较次数是log2N。

        最差情况下,二叉搜索树退化为单支树(或者类似单支),其比较次数为N

        因此,我们接下来要学习平衡二叉搜索树(AVL树、红黑树),这个就使二叉搜索树趋近于完全二叉树,尽可能保证搜索效率。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

冰果滴

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

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

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

打赏作者

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

抵扣说明:

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

余额充值