C++——搜索二叉树

搜索二叉树

本章思维导图:
在这里插入图片描述注:本章思维导图对应的.xmind和·.png文件都已同步导入至资源,可供免费查阅


1. 基本特点

下图就是一棵经典的搜索二叉树:

在这里插入图片描述

我们规定,例如上图的搜索二叉树中,“48, 25”等这些数字称为每个节点的键值(key),键值用于确定每个节点在搜索二叉树的位置

可以发现,搜索二叉树具有这样的特点:

  • 左子树每个节点的键值都比根小;右子树每个节点的键值都比根大
  • 如果对搜索二叉树进行中序遍历,那么得到的键值排列是一个升序序列。例如上图:10 25 34 48 61 73 81
  • 由于键值确定了每个节点在树中的位置,因此键值是唯一的,不能重复

2. 两种模式

搜索二叉树基于节点的存储方式可以分为两种不同的模式:**基于键(key)的搜索二叉树和基于键值对(key_value)**的搜索二叉树

2.1 基于键(key)的搜索二叉树

这种结构的搜索二叉树的节点只存储该节点的键值(key),而不存储与键值相关的值value。其结构大致如下:

template<class K>
struct BSTreeNode
{
    typedef BSTreeNode<K> Node;

    Node* _left;	//左孩子
    Node* _right;	//右孩子
    K _key;			//键值
	
    //构造
    BSTreeNode(const K& key)
        : _left(nullptr)
        , _right(nullptr)
        , _key(key)
    {}
};

由于该结构的节点只存储了用于确定位置的键值(key),因此通常用于搜索二叉树节点的查找、删除、插入,而不用于修改操作

现实生活中的应用:

居民小区的汽车门禁系统就可以使用基于键(key)的搜索二叉树

  • 小区内住户的汽车车牌号(key)会被录入小区的门禁系统
  • 当外来车辆要进入时,系统会查找该汽车的车牌号是否被录入了系统,如果没有,就不能放行
  • 同样,当车辆要驶出时,系统同样会查找该汽车的车牌号是否被录入了系统,如果没有,就不能放行

2.2 基于键值对(key_value)的搜索二叉树

这种结构的搜索二叉树的节点不仅存储了该节点的键值(key),也存储了与键值相关联的value值。其结构大致如下:

template<class K, class V>
struct BSTreeNode
{
    typedef BSTreeNode<K, V> Node;

    Node* _left;	//左孩子
    Node* _right;	//右孩子
    K _key;			//键值
    V _value;		//value值
	
    //构造
    BSTreeNode(const K& key, const V& value)
        : _left(nullptr)
        , _right(nullptr)
        , _key(key)
        , _value(value)
    {}
};

由于这种结构的节点存储了与键值(key)相关联的值,因此既可以进行节点的查找,插入,删除操作,也可以对键值(key)对应的value进行修改

现实生活中的应用:

商场的停车场就可以使用基于键值对(key_value)的搜索二叉树

  • 当汽车要驶入商场的停车场时,系统会记录汽车的车牌号(key)以及进入时间(value)
  • 当汽车要驶出商场的停车场时,系统会查找汽车的车牌号(key),通过key来找到进入的时间(value),从而计算出停车费

3. 基本操作

我们以基于键(key)的搜索二叉树为例,来学习搜索二叉树的查找节点、新增节点、删除节点的操作:

3.1 查

查找键值为key的节点

基于搜索二叉树“左子树每个节点的键值都比根小;右子树每个节点的键值都比根大”的特性我么可以很容易的对一个节点进行查找:

  • 如果查找到节点的键值比根的键值大,那么就去跟得右子树找
  • 如果查找到节点的键值比根的键值小,那么就去跟得左子树找

例如:

在这里插入图片描述

非递归版本:

//找到了就返回真true
//没找到就返回假false
bool 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 true;
    }

    return false;
}

递归版本:

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

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

/*
也可以写成:
bool _findR(Node* root, const K& key)
{
	if (root == nullptr)
		return false;

	return root->_key == key || _findR(root->_left, key) || _findR(root->_right, key);
}
*/

3.2 增

新增节点,该节点的键值为key

3.2.1 非递归

要新增节点,首先就要找到新增节点的位置。这和查找节点的步骤类似:

  • 当走到空nullptr时,该位置就是新增节点的位置
  • 但需要注意,如果在查找的过程中碰到已有节点的键值等于key的情况,就不能继续插入了(键值唯一)
Node* cur = _root;
while (cur)
{
    parent = cur;
    if (cur->_key > key)
        cur = cur->_left;
    else if (cur->_key < key)
        cur = cur->_right;
    else
        return false;
}

在这里插入图片描述

但是,这只是找到了新增节点插入的位置,我们还需要建立父亲和孩子的连接因此在找插入位置cur的同时也需要保存cur的父亲parent。完整的代码为:

bool insert(const K& key)
{
    Node* newNode = new Node(key);

    if (_root == nullptr)
    {
        _root = newNode;
        return true;
    }

    Node* parent = _root;	//保存插入位置的父亲
    Node* cur = _root;		//寻找插入位置
    while (cur)
    {
        parent = cur;
        if (cur->_key > key)
            cur = cur->_left;
        else if (cur->_key < key)
            cur = cur->_right;
        else
            return false;
    }
	
    //如果key大于父亲,就插在父亲的右边
    //否则插在父亲的左边
    if (key > parent->_key)
        parent->_right = newNode;
    else
        parent->_left = newNode;

    return true;
}

3.2.2 递归

递归版本同样也面临着新增节点和父亲的连接问题。

解决办法之一就是向函数的形参中加一个参数,用于保存父节点,但其实还有另一种更巧妙的方法:

我们可以利用引用,将待插入的nullptr直接变为新增的节点,这样就可以省去连接的步骤了:

在这里插入图片描述

bool _insertR(Node*& root, const K& key)
{
    if (root == nullptr)
    {
        Node* newNode = new Node(key);
        root = newNode;

        return true;
    }

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

3.3 删

删除键值为key的节点

3.3.1 非递归

第一步肯定是要先找到要被删除的节点,同时为了保证删除后搜索二叉树结构的正确性和完整性,肯定需要保存该节点的父亲来连接这个节点的左右孩子

Node* parent = _root;
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
    {
        //开始插入
    }
}

根据被删除节点的左右孩子可以分为三种情况:

情况一——被删除的节点没有左右孩子

在这里插入图片描述

则,如果被删除的节点为父亲的左孩子,则让父亲的左孩子连接到被删除节点的任意一个nullptr即可;被删除节点为父亲的右孩子同理

在这里插入图片描述

情况二——被删除的节点有一个孩子

在这里插入图片描述

此时,我们可以将被删除节点的孩子托管给父亲。具体分析如下:

如果被删除节点为父亲的右孩子,那么:

  • 如果被删除节点的左孩子不为空,就将被删除节点的左孩子连接到父亲的右孩子
  • 如果被删除节点的右孩子不为空,就将被删除节点的右孩子连接到父亲的右孩子

如果被删除节点为父亲的左孩子,那么:

  • 如果被删除节点的左孩子不为空,就将被删除节点的左孩子连接到父亲的左孩子
  • 如果被删除节点的右孩子不为空,就将被删除节点的右孩子连接到父亲的左孩子

在这里插入图片描述

情况三——被删除的节点左右孩子都有

在这里插入图片描述

此时就不能将左右孩子托管给父亲了,因为父亲可能连接了其他节点

因此,我们需要采取一种新方法——替换法:

我们需要在被删除节点的左子树或右子树找到一个节点,并替换被删除的节点

该节点满足的条件是:键值大于左子树的所有节点,小于右子树的所有节点

因此,有两个满足该条件的节点:左子树最右边的节点(即左子树的最大节点);右子树最左边的节点(即右子树的最小节点)

在这里插入图片描述

实现代码:

bool erase(const K& key)
{
    assert(_root);

    Node* parent = _root;
    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 == parent)
                    _root = cur->_right;
                else if (cur == parent->_left)
                    parent->_left = cur->_right;
                else
                    parent->_right = cur->_right;

                delete cur;
                return true;
            }
            //如果被删除节点的右子树为空
            else if (cur->_right == nullptr)
            {
                if (cur == parent)
                    _root = cur->_left;
                else if (cur == parent->_left)
                    parent->_left = cur->_left;
                else
                    parent->_right = cur->_left;

                delete cur;
                return true;
            }
            //如果被删除的节点左右子树都存在
            else
            {
                //找到右子树的最小节点和最小节点的父亲
                Node* rightMin = cur->_right;
                Node* rightParent = cur;
                while (rightMin->_left)
                {
                    rightParent = rightMin;
                    rightMin = rightMin->_left;
                }
				
                //将被删除节点的键值key替换为右子树的最小节点的键值
                cur->_key = rightMin->_key;
				
                //保持右子树的最小节点的子树的连接
                if (rightMin == rightParent->_right)
                    rightParent->_right = rightMin->_right;
                else
                    rightParent->_left = rightMin->_right;

                delete rightMin;
                return true;
            }
        }
    }

    return false;
}

3.3.2 递归

这里为了处理好被删除节点的子树和被删除节点的父亲的连接关系,同样需要用到引用:

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

    if (root->_key > key)
        return _eraseR(root->_left, key);
    else if (root->_key < key)
        return _eraseR(root->_right, key);
    else
    {
        //记录要被删除的节点
        Node* del = root;

        if (root->_left == nullptr)
            root = root->_right;
        else if (root->_right == nullptr)
            root = root->_left;
        else
        {
            //找到右子树的最小节点
            Node* rightMin = root->_right;
            while (rightMin->_left)
                rightMin = rightMin->_left;
			
            //交换被删除节点和右子树最小节点的键值
            swap(root->_key, rightMin->_key);
			
            //执行到这一步,要被删除的键值为key的节点一定不会有两棵子树
            //继续递归到右子树即可
            return _eraseR(root->_right, key);
        }

        delete del;
        return true;
    }
}

4. 基于键(key)的搜索二叉树的实现代码

namespace key
{
	template<class K>
	struct BSTreeNode
	{
		typedef BSTreeNode<K> Node;

		Node* _left;
		Node* _right;
		K _key;

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

	template<class K>
	class BSTree
	{
	public:
		typedef BSTreeNode<K> Node;

		BSTree() = default;	//强制生成构造

		BSTree(const BSTree<K>& tree)
		{
			_root = copy(tree._root);
		}

		BSTree<K>& operator= (BSTree<K> tree)
		{
			swap(_root, tree._root);

			return *this;
		}

		~BSTree()
		{
			destory(_root);
		}

		void inorder()
		{
			_inorder(_root);
			cout << endl;
		}

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

			return false;
		}

		bool insert(const K& key)
		{
			Node* newNode = new Node(key);

			if (_root == nullptr)
			{
				_root = newNode;
				return true;
			}

			Node* parent = _root;
			Node* cur = _root;
			while (cur)
			{
				parent = cur;
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return false;
			}

			if (key > parent->_key)
				parent->_right = newNode;
			else
				parent->_left = newNode;

			return true;
		}

		bool erase(const K& key)
		{
			assert(_root);

			Node* parent = _root;
			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 == parent)
							_root = cur->_right;
						else if (cur == parent->_left)
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;

						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == parent)
							_root = cur->_left;
						else if (cur == parent->_left)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;

						delete cur;
						return true;
					}
					else
					{
						Node* rightMin = cur->_right;
						Node* rightParent = cur;
						while (rightMin->_left)
						{
							rightParent = rightMin;
							rightMin = rightMin->_left;
						}

						cur->_key = rightMin->_key;

						if (rightMin == rightParent->_right)
							rightParent->_right = rightMin->_right;
						else
							rightParent->_left = rightMin->_right;

						delete rightMin;
						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);
		}

		
private:
        Node* _root = nullptr;
        
		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newNode = new Node(root->_key);
			newNode->_left = copy(root->_left);
			newNode->_right = copy(root->_right);

			return newNode;
		}

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

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

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

			_inorder(root->_left);
			cout << root->_key << ' ';
			_inorder(root->_right);
		}

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

			return root->_key == key || _findR(root->_left, key) || _findR(root->_right, key);
		}

		bool _insertR(Node*& root, const K& key)
		{
			if (root == nullptr)
			{
				Node* newNode = new Node(key);
				root = newNode;

				return true;
			}

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

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

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

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

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

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

				delete del;
				return true;
			}
		}
	};
}

5. 基于键值对(key_value)的搜索二叉树

key_value搜索二叉树的实现和key搜索二叉树的实现基本相同,只有查找结点和新增节点这两个操作有所不同,故下面只展示这两部分:

namespace key_value
{
	template<class K, class V>
	struct BSTreeNode
	{
		typedef BSTreeNode<K, V> Node;

		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
	{
	public:
		typedef BSTreeNode<K, V> Node;

		BSTree() = default;

		BSTree(const BSTree<K, V>& tree)
		{
			_root = copy(tree._root);
		}

		BSTree<K, V>& operator= (BSTree<K, V> tree)
		{
			swap(_root, tree._root);

			return *this;
		}

		~BSTree()
		{
			destory(_root);
		}

		void inorder()
		{
			_inorder(_root);
			cout << endl;
		}

		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 insert(const K& key, const V& value)
		{
			Node* newNode = new Node(key, value);

			if (_root == nullptr)
			{
				_root = newNode;
				return true;
			}

			Node* parent = _root;
			Node* cur = _root;
			while (cur)
			{
				parent = cur;
				if (cur->_key > key)
					cur = cur->_left;
				else if (cur->_key < key)
					cur = cur->_right;
				else
					return false;
			}

			if (key > parent->_key)
				parent->_right = newNode;
			else
				parent->_left = newNode;

			return true;
		}

		bool erase(const K& key)
		{
			assert(_root);

			Node* parent = _root;
			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 == parent)
							_root = cur->_right;
						else if (cur == parent->_left)
							parent->_left = cur->_right;
						else
							parent->_right = cur->_right;

						delete cur;
						return true;
					}
					else if (cur->_right == nullptr)
					{
						if (cur == parent)
							_root = cur->_left;
						else if (cur == parent->_left)
							parent->_left = cur->_left;
						else
							parent->_right = cur->_left;

						delete cur;
						return true;
					}
					else
					{
						Node* rightMin = cur->_right;
						Node* rightParent = cur;
						while (rightMin->_left)
						{
							rightParent = rightMin;
							rightMin = rightMin->_left;
						}

						cur->_key = rightMin->_key;

						if (rightMin == rightParent->_right)
							rightParent->_right = rightMin->_right;
						else
							rightParent->_left = rightMin->_right;

						delete rightMin;
						return true;
					}
				}
			}

			return false;
		}

		Node* findR(const K& key)
		{
			return _findR(_root, key);
		}

	private:
		Node* _root = nullptr;

		Node* _findR(Node* root, const K& key)
		{
			if (root == nullptr)
				return nullptr;

			Node* ret = nullptr;
			if (root->_key > key)
				ret = _findR(root->_left, key);
			else if (ret == nullptr && root->_key < key)
				ret = _findR(root->_right, key);
			else
				ret = root;

			return ret;
		}

		Node* copy(Node* root)
		{
			if (root == nullptr)
				return nullptr;

			Node* newNode = new Node(root->_key);
			newNode->_left = copy(root->_left);
			newNode->_right = copy(root->_right);

			return newNode;
		}

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

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

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

			_inorder(root->_left);
			cout << root->_key << ' ';
			_inorder(root->_right);
		}
	};
}
  • 31
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Forward♞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值