搜索二叉树

搜索二叉树的概念

二叉搜索树又称二叉排序树,它或者是一棵空树,或者是具有以下性质的二叉树:
  • 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值
  • 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值
  • 它的左右子树也分别为二叉搜索树

通俗来说,左节点的值要比其根节点的值小, 右节点的值要比其根节点的值大。

例子:

搜索二叉树的功能介绍及实现

基本函数

搜索二叉树节点

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

搜索二叉树的成员变量

Node* _root;

构造函数

	BSTree(Node* root = nullptr)
	{
		_root = root;
	}

析构函数

	~BSTree()
	{
		Destroy(_root);
	}
	void Destroy(Node* root)//后序遍历删除
	{
		if (root == nullptr)
			return;

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

拷贝构造函数

        这里要用深拷贝,用浅拷贝会造成析构两次的后果。

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

    		Node* newRoot = new Node(root->_key);
    		newRoot->_left = Copy(root->_left);
    		newRoot->_right = Copy(root->_right);
    		return newRoot;
    	}

        BSTree(const BSTree<K>& t)//这里要用深拷贝
		{
			_root = Copy(t._root);
		}

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

搜索二叉树的遍历

中序遍历

	void _InOrder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}

        注意直接调用InOther()需要传根节点,但是我们无法类外访问根节点(根节点访问权限是private)。

解决方法:

1.套一层函数,在类内对根节点进行访问。

	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

使用:

    BSTree<int> t;
	int arr[] = { 8,3,1,10,6,4,7,14,13 };
	for (auto e : arr)
	{
		t.insert(e);
	}
    t._InOrder();

2.写一个获取根节点的函数,实现在类外访问根节点。 

	Node* GetRoot()
	{
		return _root;
	}

使用:

    BSTree<int> t;
	int arr[] = { 8,3,1,10,6,4,7,14,13 };
	for (auto e : arr)
	{
		t.insert(e);
	}
    t._InOrder(t.GetRoot());

缺陷是类外访问了private的成员,不安全。 

搜索二叉树的查找

非递归版本

        直接进行比较,将插入的key值与cur节点的值进行比较,比cur节点大将cur更新为右孩子节点,比cur节点小就将cur更新为左孩子节点,直到遇到key值与cur节点值相同的情况,返回true,若最后遇到空则返回false。

	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 FindR(const K& key)
		{
			return _FindR(_root, key);
		}

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

搜索二叉树的插入

功能:给一个值,插入在二叉搜索树合适的位置(不能插入相同的值)。

非递归版本

        直接进行比较,将插入的key值与cur节点的值进行比较,向左右节点进行移动直到遇到空时插入到该位置,此过程需要保存上一个节点的位置用来进行插入节点。

	bool insert(const K& key)
	{
		//检查空树,防止parent==null时直接访问其成员
		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;
		}
	}

递归版本

		bool InsertR(const K& key)
		{
			return _InsertR(_root, key);
		}
        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;
			}
		}

搜索二叉树的删除

        功能:给定一个值(这里是查找的功能,实际上找到了的是这个值所在的节点),删除该节点

非递归版本

首先是要找到要删除的节点,这一步与之前查找一样,我们把大致的框架搭建起来:

        Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)//待找节点比cur节点值大
			{
				parent = cur;//存一下父节点
				cur = cur->_right;//cur移动到右节点
			}
			else if (cur->_key > key)//待找节点比cur节点值大
			{
				parent = cur;
				cur = cur->_left;
			}
			else//找到了该节点
			{
                //对节点进行删除
            }
        }

下面对删除节点进行分析

情况分析:对于要删除的节点有三种情况

1.该节点无子节点

        直接对该节点进行删除。该步骤可以归到2中,因为可以把nullptr当做一个节点连接到父节点中。

2.该节点仅有一个子节点

        托孤法删除:将该节点的子节点连接到该节点的父节点,再删除该节点。

       1.当该节点有父节点时

                根据该节点的父节点的位置不同,有两种情况:

                1.该节点是父节点的左节点

                2.该节点是父节点的右节点

                if (cur->_left == nullptr)
				{

						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}

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

						if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}

					    delete cur;
					    return true;
				}

        2.当该节点无父节点时,此时该节点是根

				if (cur->_left == nullptr)
				{
					if (cur == _root)//直接删根时,将根进行转让
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}

					delete cur;
					return true;
				}
				else if (cur->_right == nullptr)
				{
					if (cur == _root)//直接删根时,将根进行转让
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_left;
						}
						else
						{
							parent->_left = cur->_left;
						}
					}

					delete cur;
					return true;
				}

3.该节点有两个子节点

        无法用托孤法将孩子节点连接到父亲节点,因为有两个孩子都连接到父节点,可能会导致父节点出现三个子节点的情况,不符合二叉树的结构。

        替换法删除:找一个能替换“要删除的节点”的节点a,交换值,转换为删除a节点。

        什么节点能替换?

  • 左子树的最大节点(左子树的最右节点
  • 右子树的最小节点(右子树的最左节点

        原因:(以左子树的最左节点为例)以要删除的节点为基准,这个节点的值一定小于右子树的所有节点的值,大于左子树的所有节点。这时找到左子树的最左节点,记为Mi点,该节点是左子树的最小的节点,但也大于基准节点的值,这个Mi节点与基准点是相邻的(按大小关系来说),交换两个相邻的节点不改变其他位置的顺序关系,也就是仅增加一个逆序数,这个时候将Mi点删除后,这个逆序数消失,所有数的相对位置关系都变成顺序。

        替换法的代码如下:

					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->_left;
					}
					else
					{
						rightMinParent->_right = rightMin->_right;
					}
					delete rightMin;
					return true;

        综合这几种情况,总代码如下: 

	bool Erase(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			if (cur->_key < key)//待找节点比cur节点值大
			{
				parent = cur;//存一下父节点
				cur = cur->_right;//cur移动到右节点
			}
			else if (cur->_key > key)//待找节点比cur节点值大
			{
				parent = cur;
				cur = cur->_left;
			}
			else//找到了该节点
			{
				//进行删除
				if (cur->_left == nullptr)
				{
					if (cur == _root)//直接删根时
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_right)
						{
							parent->_right = cur->_right;
						}
						else
						{
							parent->_left = cur->_right;
						}
					}

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

					delete cur;
					return true;
				}
				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->_left;
					}
					else
					{
						rightMinParent->_right = rightMin->_right;
					}
					delete rightMin;
					return true;
				}
			}
		}
		return false;
	}

递归版本

		bool EraseR(const K& key)
		{
			return _EraseR(_root, key);
		}
        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->_right == nullptr)
				{
					root = root->_left;
				}
				else if (root->_left == nullptr)
				{
					root = root->_right;
				}
				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;
			}
		}

搜索二叉树的类型

K模型:快速查找一个值在不在

        上文中就是一个K模型,给定key值查找的是key值。

        例子:彩票,直接通过号码在库中查找是否中奖。

KV模型:快速通过一个值(key)查找另一个值(value)在不在?

        在节点的结构体中加入一个value值,这样通过key值查找到节点时,可以访问节点的value值,从而实现了通过一个值(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 K& value)
		:_left(nullptr)
		, _right(nullptr)
		, _key(key)
		,_value(value)
	{
	}
};

        例子:英文字典,通过英文查找释意。

搜索二叉树性能分析

        对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树查找长度是节点在二叉搜索树的高度,即节点越深,则比较次数越多。
        最优情况:二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为:log^N_{2}
       

        最坏情况:二叉搜索树退化为单支树(或者类似单支),其平均比较次数为:$\frac{N}{2}$

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

橘子13

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

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

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

打赏作者

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

抵扣说明:

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

余额充值