二叉搜索树(Binary Search Tree)

概念

  1. 若左子树不为空,则左子树上所有结点的值都小于根结点
  2. 若右子树不为空,则右子树上所有结点的值都小于根结点
  3. 它的左右子树也分别为二叉搜索树
  4. 空树也是二叉搜索树

二叉搜索树结点的定义

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:
    void _Inorder(Node* root);
    void Inorder();
    bool Insert(const K& key);
    Node* Find(const K& key);
    bool Erase(const K& key);
private:
	Node* _root = nullptr;
};

二叉搜索树的中序遍历

void _Inorder(Node* root)
{
	if (root == nullptr)
		return;
	_Inorder(root->_left);
	cout << root->_key << ' ';
	_Inorder(root->_right);
}
void Inorder()
{
	_Inorder(_root);
	cout << endl;
}

由于在类的成员变量_root外部无法访问, 所以再定义一个_Inorder函数

二叉搜索树的插入

	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
        // 在二叉树中搜索要插入的位置,最后cur为空,就是要插入的位置,此时parent为cur的父节点。
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
            // 当key大于结点,往右搜索要插入的位置
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->_right;
			}
            // 当key小于结点,往左搜索要插入的位置
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->_left;
			}
            // 当key等于结点,那么不插入,返回false
			else
			{
				return false;
			}
		}
        // cur此时为空结点,该位置就是要插入的位置
		cur = new Node(key);
        // 父节点小于key,插入到父节点的右边
		if (parent->_key < key)
		{
			parent->_right = cur;
		}
        // 父节点大于key,插入到父节点的左边
		else
		{
			parent->_left = cur;
		}
		return true;
	}

如果二叉树中有一个结点等于key,那么就不插入,返回false。

二叉树搜索树的查找

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

二叉搜索树的删除

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
		{
			 //找到了要删除的结点,cur
			 //分为三种情况删除
			//1、左为空,父亲指向cur的右
			//2、右为空,父亲指向cur的左
			//3、左右都不为空, 
			if (cur->_left == nullptr)
			{
				// 当要删除的结点为根结点时
				if (cur == _root)
				{
					_root = cur->_right;
				}
				else
				{
					// 当删除的结点为右结点时
					if (parent->_right == cur)
						parent->_right = cur->_right;
					// 当删除的结点为左结点时
					else
						parent->_left = cur->_right;
				}
				delete cur;
			}
			else if (cur->_right == nullptr)
			{
				// 当要删除的结点为根结点时
				if (cur == _root)
				{
					_root = cur->_left;
				}
				else
				{
					// 当删除的结点为右结点时
					if (parent->_right == cur)
						parent->_right = cur->_left;
					// 当删除的结点为左结点时
					else
						parent->_left = cur->_left;
				}
				delete cur;
			}
			// 删除的结点左右都不为空
			else
			{
				 //找右子树的最左结点,将该结点覆盖要删除的结点
				// 当然也可以找左子树最右结点
				Node* rightMinParent = cur;
				Node* rightMin = cur->_right;
				// 循环找右子树最左的结点
				while (rightMin->_left)
				{
					rightMinParent = rightMin;
					rightMin = rightMin->_left;
				}
				//替代
				// cur:要删除的结点;rightMin:右子树最小的结点,直接覆盖cur
				cur->_key = rightMin->_key;
				//删除
				// 此时rightMin为叶子结点
				if (rightMin == rightMinParent->_left)
					rightMinParent->_left = rightMin->_right;
				else
					rightMinParent->_right = rightMin->_right;
				delete rightMin;

			}
			return true;
		}
	}
	 //此时树结点都找完了,但是没有要删除的结点,返回false
	return false;
}

删除分为三种情况:(其实还有一种:删除的结点左右都为空。这种情况直接可以当成1or2处理)

  1. 删除的结点左为空 
  2. 删除的结点右为空
  3. 删除的结点左右都不为空

注意:删除一个结点,需要找到该结点的父结点才能进行删除操作。所以在情况1or2,要对删除的结点是否为根结点特判。
情况3是最复杂的一个,需要找到该结点的右子树的最左结点进行覆盖,然后删除最左结点(当然寻找左子树的最右结点也行)。所以情况3不需要对根结点进行特判。

对于情况3,找左子树的最右结点

Node* leftMinParent = cur;
Node* leftMin = cur->_left;
while (leftMin->_right)
{
	leftMinParent = leftMin;
	leftMin = leftMin->_right;
}
cur->_key = leftMin->_key;
if (leftMin == leftMinParent->_left)
				leftMinParent->_left = leftMin->_left;
else
				leftMinParent->_right = leftMin->_left;
delete leftMin;

二叉搜索树的KV模型应用

二叉树搜索树的每一个结点还可以存一个值(value),这样就可以用key去映射这个value值,从而可以应用在一些场景。

整体的代码实现就是在二叉搜索树的结点种添加一个value。

#pragma once
#include<string>
#include<iostream>
using namespace std;

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:
	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)
		{
			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, 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 << ' ';
		_Inorder(root->_right);
	}
	void Inorder()
	{
		_Inorder(_root);
		cout << endl;
	}

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

	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
			{
				// 找到了要删除的结点,cur
				// 分为三种情况删除
				//1、左为空,父亲指向cur的右
				//2、右为空,父亲指向cur的左
				//3、左右都不为空, 
				if (cur->_left == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (parent->_right == cur)
							parent->_right = cur->_right;
						else
							parent->_left = cur->_right;
					}
					delete cur;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (parent->_right == cur)
							parent->_right = cur->_left;
						else
							parent->_left = cur->_left;
					}
					delete cur;
				}

				else
				{
					// 找右子树的最左结点,将该结点覆盖要删除的结点
					Node* rightMinParent = cur;
					Node* rightMin = cur->_right;
					while (rightMin->_left)
					{
						rightMinParent = rightMin;
						rightMin = rightMin->_left;
					}
					//替代
					cur->_key = rightMin->_key;
					//删除
					if (rightMin == rightMinParent->_left)
						rightMinParent->_left = rightMin->_right;
					else
						rightMinParent->_right = rightMin->_right;
					delete rightMin;

				}
				return true;
			}
		}
		return false;
	}
private:
	Node* _root = nullptr;
};

二叉搜索树的优缺点

由于二叉搜索树的性质,增、删、查、找(注意不能改,因为改结点可能不能继续维持一颗二叉搜索树了),都是和查找的性能相关,查找的时间复杂度:O(log n)。但是当插入一个有序或者接近有序的数组,那么就会形成一颗单边树,这样就和链表一样了。为了优化这一情况,就引入了AVLTree和红黑树。后面会在我的博客中和大家介绍。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值