Bootstrap

二叉搜索树

一、了解二叉搜索树

二叉搜索树就是任意一个根节点的值比左孩子的值大,比右孩子的值小的树。

所以中序遍历二叉搜索树得到的是从小到大的数组。

当然普通的二叉搜索树搜索时间不一定是O(logN),当以有序的顺序插入普通二叉搜索树时,得到的是一个单支的搜索树,此时查找值就是O(N),所以才有后面的 AVL 树和红黑树。

今天实现普通二叉搜索树。

二、部分代码展示

#pragma once
template<class K, class V>
struct BSTreeNode
{
	BSTreeNode<K, V> _left;
	BSTreeNode<K, V> _right;
	K _key;
	V _val;

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

template<class K, class V>
class BSTree
{
	typedef BSTreeNode<K, V> Node;
public:
	bool insert(const K& key, const V& val)
	{
		if (_root == nullptr)
		{
			_root = new Node(key, val);
			return true;
		}

		Node* cur = _root;
		Node* prev = nullptr;
		while (cur)
		{
			if (cur->_key > key)
			{
				prev = cur;
				cur = cur->_left;
			}
			else if (cur->_key < key)
			{
				prev = cur;
				cur = cur->_right;
			}
			else
				return false;
		}
		Node* newnode = new Node(key, val);
		key > prev->_key ? prev->_right = newnode : prev->_left = newnode;
		return true;
	}

	Node* find(const K& key)
	{
		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* cur = _root;
		Node* prev = cur;
		while (cur)
		{
			if (cur->_key < key)
			{
				prev = cur;
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				prev = cur;
				cur = cur->_left;
			}
			else
			{
				// 2.若没有孩子或只有一个孩子
				// 问题:如果删的是头节点,头节点没有父亲,只能另外判断
				if (cur->_left == nullptr || cur->_right == nullptr)
				{
					if (cur->_left)
					{
						if (_root == cur)
							_root = cur->_left;
						else
							cur == prev->_left ? prev->_left = cur->_left : prev->_right = cur->_left;
					}
					else
					{
						if (_root == cur)
							_root = cur->_right;
						else
							cur == prev->_left ? prev->_left = cur->_right : prev->_right = cur->_right;
					}
					delete cur;

				}
				// 3.有两个孩子,替代法,找左树的最右边或右树的最左边,交换删除节点和前面两个的任意一个
				// 不要忘记交换的节点的孩子
				// 若被删节点的右子树的最左节点一开始不存在会导致两个问题:
				// 第一,循环进不去,rightMinP不会更新,所以一开始rightMinP就要是rightMin的父节点cur
				// 第二,正常如果有rightMin->_left,rightMin在rightMinP的左边,如果没有就在右边
				//       所以最后更新一个是P的左子树等于rightMin右子树,一个是右子树等于右子树
				else
				{
					Node* rightMin = cur->_right;
					Node* rightMinP = cur;
					while (rightMin->_left)
					{
						rightMinP = rightMin;
						rightMin = rightMin->_left;
					}
					std::swap(cur->_key, rightMin->_key);

					// 由于是右树的左孩子,所以只要关心他的右孩子
					if(rightMin == rightMinP->_left)
						rightMinP->_left = rightMin->_right;
					else 
						rightMinP->_right = rightMin->_right;
					delete rightMin;
				}
				return true;
			}
		}
		return false;
	}
private:
	Node* _root = nullptr;
};

三、插入函数

1、思路

插入很简单,比根大向右走,比根小向左走,遇到空就插入。

2、问题

(1)遇到相等?

一般的二叉搜索树不支持存储相等 key,所以直接返回 false

(2)一开始根节点是 nullptr?

添加根节点为插入值。

四、删除函数

1、思路

情况措施
删除节点没有孩子父节点指向空
删除节点有一个孩子父节点指向删除节点的唯一孩子
删除节点有两个孩子替换法

替换法:删除节点的孩子中找到能顶替删除节点的,那必须是比原本左子树的根大,比右子树的根小,那就是右子树的最左节点或左子树的最右节点,代码里展示的是右子树的最左节点。找到后和删除节点交换值,然后把右子树最左节点的右孩子(都是最左节点了肯定只有一个右孩子)交给他的父节点。

优化:节点只有一个孩子和没有孩子的情况可以一起考虑,因为当没有右孩子时就让父节点指向左孩子,此时如果删除节点没有孩子,左孩子也是空,不影响结果,同理没有左孩子也是一样。所以是没有孩子的情况可以复用只有一个孩子情况的代码。

2、问题

(1)一开始删除的是根节点且根节点孩子少于两个?

如下图:

由于根节点没有父亲,所以不做判断会出错。把 _root 传给下一个孩子就行了。

(2)一开始删除节点的右子树没有最左节点?

如果一开始删除节点的右子树有最左节点,即我们现在要删3

那我们首先要找右子树最左节点,定义 rightMin 是6,rightMin 的父节点 rightMinP 是3,最后经过循环找到右子树的最左节点是4,即此时 rightMin 是4,rightMinP是6

接下来逻辑是 swap(cur->_val, rightMin->_val); rightMinP->_left = rightMin->_right;

但是上面的逻辑并不适用于一开始没有左子树的情况,即现在我们要删10

没有左子树会出现两个问题:

a、找左子树的循环进不去,父节点不更新。

所以我们一开始定义 rightMinP 是 cur,即8

b、此时删除节点的右孩子充当了右子树的最左节点

但是上面的代码 rightMinP->_left = rightMin->_right; 逻辑明显有问题。

应该是 rightMinP->_right = rightMin->_right; 需要特判解决。

;