搜索二叉树

本文详细介绍了搜索二叉树的概念、非递归和递归实现方法,包括查找、插入和删除操作,并探讨了其在key在不在和key-value问题中的应用。
摘要由CSDN通过智能技术生成

目录

搜索二叉树概念

搜索二叉树模拟实现

非递归实现

递归实现

搜索二叉树应用场景

1.key在不在的问题

2.key-value的问题


搜索二叉树概念

二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树:

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

tips:搜索二叉树的中序遍历是升序!

搜索二叉树的查找效率最好是logN(接近满二叉树), 最坏是N(当插入的数据接近有序时)

搜索二叉树模拟实现

搜索二叉树节点结构的定义

template <class K>
struct BSTreeNode
{
	K _key;
	BSTreeNode<K>* _left;
	BSTreeNode<K>* _right;

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

搜索二叉树函数声明

template <class K>
class BSTree
{
	typedef BSTreeNode<K> Node;
public:
	//查找
	bool Find(const K& key);
	//插入
	bool Insert(const K& key);
	//删除
	bool Erase(const K& key);
	//中序遍历
	void _InOrder(Node* root);
	//解决类外部拿不到_root的问题
	void InOrder();
	//析构函数
	~BSTree();
	void Destory(Node*& root);

	BSTree() {}; //默认构造,防止编译器报错
	//BSTree()  = default; //强制生成默认构造函数

	//拷贝构造
	BSTree(const BSTree<K>& t);
	Node* Copy(Node* root);

	//赋值重载
	BSTree<K>& operator=(BSTree<K> t);
private:
	Node* _root = nullptr;
};

非递归实现

查找

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

插入

1.插入的值在搜索二叉树中已经存在,我们直接返回

2.注意保存插入位置的父节点指针,因为还要链接

	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* parent = nullptr;
		Node* cur = _root;
		while (cur)
		{
			parent = cur;
			if (cur->_key < key)
			{
				cur = cur->_right;
			}
			else if (cur->_key > key)
			{
				cur = cur->_left;
			}
			else
			{
				return false; //插入的值在树中存在,直接返回false
			}
		}
		//链接
		cur = new Node(key);
		if (parent->_key < key) //比父亲大链接到右边
		{
			parent->_right = cur;
		}
		else //比父亲小链接到左边
		{
			parent->_left = cur;
		}
		return true;
	}

删除

删除的节点情况分类

a. 要删除的结点是叶子节点,没有左右孩子
b. 要删除的结点只有左孩子结点 或 只有右孩子
c. 要删除的结点有左、右孩子结点
删除的方法分类
a. 要删除的结点是叶子节点,没有左右孩子
b. 要删除的结点只有左孩子结点 或 只有右孩子
一般情况
特殊情况
注意: 在删除时,a. 可以归类到 b. ,因为叶子节点也是有孩子的,只是孩子为空而已, 刚好让删除节点的父节点指向删除节点的孩子(也就是指向了空)
c. 要删除的结点有左、右孩子结点
由于删除后要保证仍然是搜索二叉树,因此我们使用 替换法,也就是以要删除的节点为根节点,找左子树的最右节点(subRight)或者右子树的最左节点(subLeft),然后将要删除的节点值和subRight或subLeft的值进行交换,于是就转化成了删除只有1个孩子的节点的问题
原因: 由于左子树的最右节点是左子树中最大的,而左子树的所有值都小于右子树,所以交换之后能保证搜索二叉树的特性,右子树的最左节点同理

注意:右子树的最左节点不一定是它父亲的左孩子, 因此交换之后仍然要判断右子树的最左节点是父亲的左还是父亲的右

	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
			{
				//1.要删除的节点左为空, 让父亲指向cur的右
				if (cur->_left == nullptr)
				{
					//要删除的节点是根节点
					if (cur == _root)
					{
						_root = cur->_right;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_right;
						}
						else
						{
							parent->_right = cur->_right;
						}
					}
				}
				//2.要删除的节点右为空, 让父亲指向cur的左
				else if (cur->_right == nullptr)
				{
					//要删除的节点是根节点
					if (cur == _root)
					{
						_root = cur->_left;
					}
					else
					{
						if (cur == parent->_left)
						{
							parent->_left = cur->_left;
						}
						else
						{
							parent->_right = cur->_left;
						}
					}
				}
				//3.要删除的节点左右都不为空
				else
				{
					Node* parent = cur;
					Node* subLeft = cur->_right;
					while (subLeft->_left)
					{
						parent = subLeft;
						subLeft = subLeft->_left;
					}
					swap(cur->_key, subLeft->_key);
					
					if (subLeft == parent->_left)
						parent->_left = subLeft->_right;
					else
						parent->_right = subLeft->_right;
				}
				return true;
			}
		}
		return false;
	}

注意:要删除的节点左右都不为空时, parent开始不能设为nullptr, 正如下图所示图片,如果删的是8, parent为nullptr, while循环进不去,parent仍然为空指针,空指针解引用代码崩溃!而让parent = cur 刚好可以解决下图所示的场景

递归实现

中序遍历

注意,中序遍历递归一定是要有参数的,而如果有了root参数,类外面无法拿到_root, 因为是私有的,所以再提供一个函数不带参的,然后去调用带参函数,此时C++代码如下写法:

	//中序遍历
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		_InOrder(root->_left);
		cout << root->_key << " ";
		_InOrder(root->_right);
	}
	//解决类外部拿不到_root的问题
	void InOrder()
	{
		_InOrder(_root);
		cout << endl;
	}

查找

key比root->key大, 转化成去右子树查找,key比root->key小, 转化成去左子树查找

	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比root->key大, 转化成去右子树插入,key比root->key小, 转化成去左子树插入

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

这里重点体会参数root给引用的妙处,当最后的root是nullptr时,同时也是父节点的左指针或右指针的别名,所以root = new Node(key) 直接就把新开辟的节点和父节点链接起来了!!!

删除

要删除的key比root->key大, 转化成去右子树删除,key比root->key小, 转化成去左子树删除

	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
		{
			if (root->_left == nullptr)
			{
				Node* del = root;
				root = root->_right;
				delete del;
				return true;
			}
			else if (root->_right == nullptr)
			{
				if (root->_left == nullptr)
				{
					Node* del = root;
					root = root->_right;
					delete del;
					return true;
				}
			}
			else
			{
				Node* subLeft = root->_right;
				while (subLeft->_left)
				{
					subLeft = subLeft->_left;
				}
				swap(root->_key, subLeft->_key);

				//转换成在子树递归删除
				return _EraseR(root->_right, key);
			}
		}
	}

当key和root->key相等时,仍然是分情况讨论

当删除的节点左为空或者右为空时,我们仍然可以使用引用巧妙解决问题!

当删除的节点左为空或者右为空的节点是根节点,也没有问题

当删除的节点左右都不为空,我们可以转化成递归子树去删除!不能转化成去递归搜索二叉树, 因为替换法之后就不一定是颗搜索二叉树了,而如图所示的子树仍然是搜索二叉树

当删除的节点左右都不为空,要删除的节点是根时也没有问题

析构函数

	~BSTree()
	{
		Destory(_root);
	}

	void Destory(Node*& root)
	{
		if (root == nullptr)
			return;
		Destory(root->_left);
		Destory(root->_right);
		delete root;
	}

拷贝构造函数

    BSTree() {}; //默认构造,防止编译器报错
	//BSTree()  = default; //强制生成默认构造函数
	
	//拷贝构造
	BSTree(const BSTree<K>& t)
	{
		_root = Copy(t._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<K>& operator=(BSTree<K> t)
	{
		swap(_root, t._root);
		return *this;
	}

搜索二叉树应用场景

1.key在不在的问题

比如门禁系统本质就是key在不在的问题

2.key-value的问题

namespace kv
{
	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)
			{
				parent = cur;
				if (cur->_key < key)
				{
					cur = cur->_right;
				}
				else if (cur->_key > key)
				{
					cur = cur->_left;
				}
				else
				{
					return false; //插入的值在树中存在,直接返回false
				}
			}
			//链接
			cur = new Node(key, value);
			if (parent->_key < key) //比父亲大链接到右边
			{
				parent->_right = cur;
			}
			else //比父亲小链接到左边
			{
				parent->_left = cur;
			}
			return true;
		}

		//查找
		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;
		}

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

	private:
		Node* _root = nullptr;
	};
}

字典中查找就是key-value的问题

int main()
{
	kv::BSTree<string, string> dict;
	dict.Insert("sort", "排序");
	dict.Insert("left", "左边");
	dict.Insert("right", "右边");
	dict.Insert("insert", "插入");
	string str;
	while (cin >> str)
	{
		kv::BSTreeNode<string, string>* ret = dict.Find(str);
		if (ret)
		{
			cout << ret->_value << endl;
		}
		else
		{
			cout << "字典中不存在" << endl;
		}
	}
}

 key-value模型也可以用来统计key的个数

int main()
{
	string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };
	kv::BSTree<string, int> countTree;
	for (auto& e : arr)
	{
		kv::BSTreeNode<string, int>* ret = countTree.Find(e);
		if (ret == nullptr)
		{
			countTree.Insert(e, 1);
		}
		else
		{
			ret->_value++;
		}
	}

	countTree.InOrder();
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值