二叉搜索树

一、二叉搜索树的概念

一颗二叉搜索树可以为 空树 或者满足如下条件

  • 左子树的所有结点的值小于根结点的值
  • 右子树的所有结点的值大于根结点的值
  • 左右子树均为二叉搜索树

在这里插入图片描述
对上述的二叉搜索树进行中序遍历的结果为: [1, 3, 4, 6, 7, 8, 10, 13, 14],因此二叉搜索树又称为二叉排序树

二、二叉搜索树的实现

1. 二叉搜索树的存储结构

// 二叉搜索树结点
template<class K>
struct BSTreeNode
{
	BSTreeNode<K>(const K& key = K())
		: _key(key)
		, _left(nullptr)
		, _right(nullptr)
	{}

	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;
};

// 二叉搜索树
template<class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	BSTree<K>()
		: _root(nullptr)
	{}

private:
	Node* _root;
};

2. 二叉搜索树的插入

在二叉搜索树中,插入节点后仍需满足二叉搜索树的性质,如果插入的值在二叉搜索树中已经存在,则插入失败

时间复杂度 O(N): 因为二叉搜索树可能只有左子树或右子树

// 非递归版本
bool Insert(const K& key)
{
	// 树为空时
	if (_root == nullptr)
	{
		_root = new Node(key);

		return true;
	}

	// 为了找到插入位置后可以链接,需要保存父节点
	Node* parent = nullptr;
	Node* cur = _root;

	// 找插入位置
	while (cur)
	{
		// 保存父节点的位置
		parent = cur;

		// 当前结点值 大于 key 时,则插入位置在左子树
		// 当前结点值 小于 key 时,则插入位置在右子树
		// 当前结点值 等于 key 时,则插入失败
		if (cur->_key > key) cur = cur->_left;
		else if (cur->_key < key) cur = cur->_right;
		else return false;
	}

	// 链接:判断插入位置在左子树还是右子树
	cur = new Node(key);
	if (parent->_key > key) parent->_left = cur;
	else parent->_right = cur;

	// 插入成功
	return true;
}

// 递归版本
bool Insert(const K& key)
{
	// 调用子函数递归
	return _Insert(_root, key);
}

// 插入递归
static bool _Insert(Node*& root, const K& key)
{
	if (root == nullptr)
	{
		root = new Node(key);

		return true;
	}

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

3. 二叉搜索树的删除

在二叉搜索树中,删除节点后仍需满足二叉搜索树的性质,将删除结点分四种情况

  • 结点无左右孩子,可以直接删除

  • 结点无左孩子,让父节点链接自己的右孩子后删除结点
    在这里插入图片描述

  • 结点无右孩子,让父节点链接自己的左孩子后删除结点
    在这里插入图片描述

在结点无左孩子(或者无右孩子) 的情况中,如果删除的是根结点,则直接让根结点指针链接自己的右孩子(或者左孩子),然后删除结点
在这里插入图片描述

  • 结点有左右孩子,可以使用左子树的最大结点(或者右子树的最小结点) 替换该结点,然后根据结点无右孩子(或者结点无左孩子) 的情况删除结点
    在这里插入图片描述

如果删除的值在二叉树中不存在,则删除失败

时间复杂度 O(N): 因为二叉搜索树可能只有左子树或右子树

// 非递归版本
bool Erase(const K& key)
{
	// 树为空时,删除失败
	if (_root == nullptr) return false;

	Node* parent = nullptr;
	Node* cur = _root;
	while (cur)
	{
		if (cur->_key > key)
		{
			// 保存父节点的位置
			parent = cur;

			cur = cur->_left;
		}
		else if (cur->_key < key)
		{
			// 保存父节点的位置
			parent = cur;

			cur = cur->_right;
		}
		else
		{
			// 结点无左右孩子时,可以当作结点无左孩子处理
			// 结点无左孩子时,父节点链接自己的右孩子,然后删除结点
			// 结点无右孩子时,父节点链接自己的左孩子,然后删除结点
			if (cur->_left == nullptr)
			{
				// 如果是根结点,根结点指针链接自己的右孩子
				// 判断链接在父节点的左边还是右边
				if (cur == _root) _root = cur->_right;
				else if (parent->_key > key) parent->_left = cur->_right;
				else parent->_right = cur->_right;

				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				// 如果是根结点,根结点指针链接自己的左孩子
				// 判断链接在父节点的左边还是右边
				if (cur == _root) _root = cur->_left;
				else if (parent->_key > key) parent->_left = cur->_left;
				else parent->_right = cur->_left;

				delete cur; 
			}
			else
			{
				// 找到左子树的最大结点
				Node* maxLeftParent = cur;
				Node* maxLeft = cur->_left;
				while (maxLeft->_right)
				{
					maxLeftParent = maxLeft;
					maxLeft = maxLeft->_right;
				}

				// 交换
				std::swap(cur->_key, maxLeft->_key);

				// 删除结点
				if (maxLeftParent == cur) maxLeftParent->_left = maxLeft->_left;
				else maxLeftParent->_right = maxLeft->_left;

				delete maxLeft;
			}

			return true;
		}
	}

	// 删除的值不存在
	return false;
}

// 递归版本
bool Erase(const K& key)
{
	return _Erase(_root, key);
}

static bool _Erase(Node*& root, const K& key)
{
	if (root == nullptr) return false;

	if (root->_key > key) return _Erase(root->_left, key);
	else if (root->_key < key) return _Erase(root->_right, key);
	else
	{
		Node* del = root;

		if (root->_left == nullptr) root = root->_right;
		else if (root->_right == nullptr) root = root->_left;
		else
		{
			Node* minRightParent = root;
			Node* minRight = root->_right;
			while (minRight->_left)
			{
				minRightParent = minRight;
				minRight = minRight->_left;
			}

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

			if (minRightParent == root) return _Erase(minRightParent->_right, key);
			else return _Erase(minRightParent->_left, key);
		}

		delete del;

		return true;
	}
}

4. 二叉搜索树的查找和中序遍历

时间复杂度 O(N): 因为二叉搜索树可能只有左子树或右子树

// 非递归版本
bool Find(const K& key)
{
	Node* cur = _root;
	while (cur)
	{
		// 当前结点值 大于 key 时,则 key 不可能在右子树
		// 当前结点值 小于 key 时,则 key 不可能在左子树
		// 当前结点值 等于 key 时,key 存在
		if (cur->_key > key) cur = cur->_left;
		else if (cur->_key < key) cur = cur->_right;
		else return true;
	}

	// 不存在
	return false;
}

// 递归版本
bool Find(const K& key)
{
	return _Find(_root, key);
}

// 查找递归
static bool _Find(Node* root, const K& key)
{
	if (root == nullptr) return false;

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

二叉搜索树的中序遍历:

void Inorder()
{
	// 调用子函数递归
	_Inorder(_root);
	cout << endl;
}

// 中序递归
static void _Inorder(Node* root)
{
	if (root == nullptr) return;

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

5. 二叉搜索树的拷贝构造、赋值重载和析构

BSTree<K>(const BSTree<K>& bst)
	: _root(nullptr)
{
	_root = _BSTreeCopy(bst._root);
}

BSTree<K>& operator=(BSTree<K> bst)
{
	std::swap(_root, bst._root);

	return *this;
}

~BSTree<K>()
{
	_BSTreeDestroy(_root);
	_root = nullptr;
}

// 深拷贝递归
static Node* _BSTreeCopy(Node* root)
{
	if (root == nullptr) return nullptr;

	Node* rootCopy = new Node(root->_key);

	rootCopy->_left = _BSTreeCopy(root->_left);
	rootCopy->_right = _BSTreeCopy(root->_right);

	return rootCopy;
}

// 销毁递归
static void _BSTreeDestroy(Node* root)
{
	if (root == nullptr) return;

	_BSTreeDestroy(root->_left);
	_BSTreeDestroy(root->_right);

	delete root;
}

三、二叉搜索树的应用

K模型:结点只存储 key,用于快速查找 key 是否存在

判断单词 word 是否拼写错误:将词库中的所有单词作为 key 构建一颗二叉搜索树,在 K 模型中查找该单词,查找成功,则拼写正确,否则拼写错误


KV模型:结点存储 <key, value> 键值对,一个 key 值对应一个 value 值,用于通过 key 快速查找 value

英语翻译汉语:将词库中的所有单词及其翻译作为 <key, value> 构建一颗二叉搜索树(单词作为 key,单词翻译作为 value),在 KV 模型中查找该单词,查找成功,即可获取单词的中文翻译

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值