二叉搜索树(k模型、kv模型)介绍及底层实现、

二叉搜索树的概念

二叉搜索树又叫做二叉排序树,他是一个具有以下特性的二叉树。

  1. 二叉搜索树的左孩子比父节点小,右孩子比父节点大。
  2. 二叉搜索树的左子树的全部节点都小于根节点,右子树的全部节点都大于根节点。
  3. 所有节点的左右子树都为二叉搜索树
  4. 键值是唯一的,所以二叉搜索树不能有相同的键值。

在这里插入图片描述
所以可以通过中序遍历(左中右)可以得到一个从小到大升序排列的一个结果。

二叉搜索树的代码实现

二叉搜索树节点的结构

template<class T>
	struct BSTNode {
		BSTNode(const T& value = T())
			:_left(nullptr), _right(nullptr), _value(value)
		{}
		BSTNode<T>* _left;
		BSTNode<T>* _right;
		T _value;
	};

查找
查找是二叉搜索树的核心,实现了这一部分后面的删除和插入也可以直接复用这里的部分代码。直接从根节点出发,比根节点大则查找右子树,比根节点小则查找左子树,相同则返回。如果遍历完还没找到,则说明不存在此树中,返回nullptr。

		Node* Find(const T& value) {
			Node* cur = _root;
			while (cur) {
				if (value > cur->_value) cur = cur->_right;
				else if (value < cur->_value) cur = cur->_left;
				else return cur;
			}
			return nullptr;
		}

插入
插入前半段就可以复用查找的代码,我们首先要找到我们需要插入的位置(为了保证能够找到父节点还需要用一个指针指向父节点),找到了合适的位置后,我们需要判断当前的键值比父节点大还是比父节点小,来决定应该插入到父节点的左子树还是右子树。

	bool insert(const T& value) {
			Node* newnode = new Node(value);
			if (_root == nullptr) {
				_root = newnode;
				return true;
			}
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) {
				if (value > cur->_value) parent = cur, cur = cur->_right;
				else if (value < cur->_value) parent = cur, cur = cur->_left;
				//相同则返回false,因为搜索树不能存在相同数据
				else return false;
			}
			//判断cur要插入到parent的左子树还是右子树
			if (parent->_value > value) {
				parent->_left = newnode;
			}
			else {
				parent->_right = newnode;
			}
			return true;
		}

删除
首先查找元素是否在二叉搜索树中,如果不存在,则返回, 否则要删除的结点可能分下面四种情况:
a. 要删除的结点无孩子结点
b. 要删除的结点只有左孩子结点
c. 要删除的结点只有右孩子结点
d. 要删除的结点有左、右孩子结点
看起来有待删除节点有4中情况,实际情况a可以与情况b或者c合并起来,因此真正的删除过程如下:
情况b:删除该结点且使被删除节点的双亲结点指向被删除节点的左孩子结点
情况c:删除该结点且使被删除节点的双亲结点指向被删除结点的右孩子结点
情况d:在它的右子树中寻找中序下的第一个结点(关键码最小),用它的值填补到被删除节点中,再来处理该结点的删除问题

/*删除有三种情况,一种是删除叶子节点,可以直接删除
		第二种情况,如果删除的节点只有一个子树,那么删除这个节点后,就让父节点指向他的这一子树
		前两种情况可以合并处理
		第三种情况则是左右子树都不为空,此时选择一个来节点来替换他后,再删除,就可以不破坏原有结构
		如果要保持原有结构不变化,那么选择的节点必须要和删除节点在中序遍历中是连续的,而满足的只有两个节点,一个是其左子树的最大值,一个是其右子树的最小值.
*/
		bool Erase(const T& value) {
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) {
				if (value > cur->_value) parent = cur, cur = cur->_right;
				else if (value < cur->_value) 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;
					}
					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;
					}
					//都不为空、找左子树最右边的节点或者找右子树最左边的节点、然后和被删除的节点交换
					//找到右子树最小节点进行替换
					else {
						Node* min = cur->_right;
						Node* minparent = cur;
						while (min->_left) {
							minparent = min;
							min = min->_left;
						}
						swap(min->_value, cur->_value);
						if (min == minparent->_left) minparent->_left = min->_right;
						else minparent->_right = min->_right;
						delete min;
					}
					return true;
				}
			}
			return false;
		}

其他操作我就不写了、下面的kv模型的代码比较全。

二叉搜索树的应用

k模型

K模型即只有key作为关键码,结构中只需要存储Key即可,关键码即为需要搜索到的值。
比如:给一个单词word,判断该单词是否拼写正确,具体方式如下:
以单词集合中的每个单词作为key,构建一棵二叉搜索树,在二叉搜索树中检索该单词是否存在,存在则拼写正确,不存在则拼写错误。

kv模型

每一个关键码key,都有与之对应的值Value,即<Key, Value>的键值对。该种方式在现实生活中非常常见:比如英汉词典就是英文与中文的对应关系,通过英文可以快速找到与其对应的中文,英文单词与其对应的中文<word, chinese>就构成一种键值对;再比如统计单词次数,统计成功后,给定单词就可快速找到其出现的次数,单词与其出现次数就是<word, count>就构成一种键值对。
比如:实现一个简单的英汉词典dict,可以通过英文找到与其对应的中文,具体实现方式如下:
<单词,中文含义>为键值对构造二叉搜索树,注意:二叉搜索树需要比较,键值对比较时只比较Key,查询英文单词时,只需给出英文单词,就可快速找到与其对应的key。

kv模型的代码实现

namespace kvlc
{
	template<class k, class v>
	struct BSTNode
	{
		BSTNode(const k& key = k(), const v& value = v())
			:_left(nullptr), _right(nullptr), _key(key), _value(value)
		{}
		BSTNode<k, v>* _left;
		BSTNode<k, v>* _right;
		k _key;
		v _value;
	};
	template<class k, class v>
	class BSTree
	{
		typedef BSTNode<k, v> Node;
	public:
		BSTree() :_root(nullptr) {}
		~BSTree() {
			Destory(_root);
		}
		//t1(t2)
		BSTree(const BSTree& temp) : _root(nullptr) {
			_root = copy(temp._root);
		}
		//t1=t2
		BSTree& operator=(const BSTree& temp) {
			if (this != &temp) {
				Destory(_root);
				_root = copy(temp._root);
			}
			return *this;
		}
		Node* Find(const k& key) {
			Node* cur = _root;
			while (cur) {
				if (key > cur->_key) cur = cur->_right;
				else if (key < cur->_key) cur = cur->_left;
				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* cur = _root;
			Node* parent = nullptr;
			while (cur) {
				if (key > cur->_key) parent = cur, cur = cur->_right;
				else if (key < cur->_key) parent = cur, cur = cur->_left;
				else return false;
			}
			if (parent->_key > key) {
				parent->_left = newnode;
				return true;
			}
			else {
				parent->_right = newnode;
				return true;
			}
		}
		bool Erase(const k& key) {
			Node* cur = _root;
			Node* parent = nullptr;
			while (cur) {
				if (key > cur->_key) parent = cur, cur = cur->_right;
				else if (key < cur->_key) 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;
					}
					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;
					}
					//都不为空、找左子树最右边的节点或者找右子树最左边的节点、然后和被删除的节点交换
					//找到右子树最小节点进行替换
					else {
						Node* min = cur->_right;
						Node* minparent = cur;
						while (min->_left) {
							minparent = min;
							min = min->_left;
						}
						std::swap(min->_key, cur->_key);
						std::swap(min->_value, min->_value);
						if (min == minparent->_left) minparent->_left = min->_right;
						else minparent->_right = min->_right;
						delete min;
					}
					return true;
				}
			}
			return false;
		}
		Node* copy(Node* root) {
			if (root == nullptr) return nullptr;
			Node* tmp = new Node(root->_key, root->_value);
			tmp->_left = copy(root->_left);
			tmp->_right = copy(root->_right);
			return tmp;
		}
		void Destory(Node* root){
			Node* cur = root;
			if (cur == nullptr) return;
			Destory(cur->_left);
			Destory(cur->_right);
			delete cur;
			cur = 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;
	};

二叉搜索树性能分析

对于二叉搜索树,查找是其主要的功能,STL中的map和set底层也是通过平衡二叉搜索树(红黑树)实现的。插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。
最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:O(log2N)
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:O(N)
在这里插入图片描述
要解决这个问题,就得为搜索二叉树加上平衡二叉树的属性,也就是我们通常所说的AVL树。

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

繁华的梦境

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

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

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

打赏作者

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

抵扣说明:

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

余额充值