二叉搜索树(kv模型)的模拟实现

什么是二叉搜索树

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

在这里插入图片描述
如图所示二叉树就是二叉搜索树。

二叉搜索树的性能分析

插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能。
对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二叉搜索树的深度的函数,即结点越深,则比较次数越多。

但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树

在这里插入图片描述

最优情况下,二叉搜索树为完全二叉树,其平均比较次数为:log₂N
最差情况下,二叉搜索树退化为单支树,其平均比较次数为:N/2\

二叉树的中序遍历就是一个升序遍历

模拟实现搜索二叉树

给二叉树的每一个节点加上kv模型,即每一个节点有两个值,key和value,存储和查找根据key值来操作,使用时可以根据key值找到对应的value值,类似于字典,每一个词语对应一个单词。这里我们为每一个节点都加上kv

插入操作

插入的步骤,从根节点往下遍历,要插入的节点的k值大于根节点则遍历右子树,否则遍历左子树(搜索二叉树里面没有重复值),直到遍历到空,则为插入位置。遍历时要定义prev保存前驱节点方便插入。

在这里插入图片描述

新插入一个k值为3的节点的过程

代码实现:

bool Insert(const K& key, const V& value) {
		Node* cur = _root;
		Node* prev = _root;
		while (cur) {
			if (cur->_key == key) {//遍历树,树中已经有对应值,插入失败返回false
				return false;
			}
			if (key < cur->_key) {//当前节点的key > 要插入的key,遍历左子树
				prev = cur;
				cur = cur->_left;
			}
			else {//否则遍历右子树
				prev = cur;
				cur = cur->_right;
			}
		}
		Node* newNode = new Node(key, value);//创建新的节点
		if (!prev) {//当前树为空树,直接插入
			_root = newNode;
		}
		else {
			if (prev->_key > newNode->_key) {//插入对应位置
				prev->_left = newNode;
			}
			else {
				prev->_right = newNode;
			}
		}
		return true;
	}

查找

查找相对简单,遍历树,等于根节点直接返回,大于向右查找,小于向左查找,遍历结束没有找到返回空

代码实现

Node* Find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key == cur->_key) {//找到对应节点,返回该节点
				return cur;
			}
			else if (key < cur->_key) {//当前节点的key > 要查找的key,到左子树中找
				cur = cur->_left;
			}
			else {//否则到右子树中找
				cur = cur->_right;
			}
		}
		return nullptr;
	}

删除节点

删除节点比较复杂需要考虑两种情况:
第一种要删除的节点为叶子节点或者只有一个孩子
在这里插入图片描述
假设现在要删除key值为4的节点,我们可以删除他,然后让父亲节点原来指向4的域指向4的孩子(4只有一个孩子,叶子节点的孩子为空),这样就完成了删除操作。
在这里插入图片描述
删除后2的right域指向key值为3的节点。

第二种情况:要删除的节点的左右孩子存在
在这里插入图片描述
假设要删除key值为8的节点,删除8号节点以后则左子树与右子树都没有了父亲节点,所以要从他们俩里面找出一个可以替代父亲节点的节点,根据二叉搜索树的性质可得,符合条件的节点就是左子树的最大节点或者右子树的最小节点满足。但是直接删除父亲节点在让新的节点旋转至父亲节点的位置戴杰过高,所以我们可以先找到符合条件的节点,然后将各节点的值赋值到要删除的节点上,在删除原来的节点(符合条件的节点不会有两个孩子,删除该节点可复用第一种方法),这样就逻辑上删除了目标节点。

在这里插入图片描述
左子树符合要求的节点为7,则将7赋值到8,然后删除原来位置的7号节点。

代码实现:

bool Erase(const K& key) {
		Node* cur = _root;
		Node* parent = _root;
		while (cur) {
			if (key == cur->_key) {
				//找到要删除的节点
				if (cur->_left && cur->_right) {//要删除的节点有左右两个孩子
					Node* max = cur->_left;//找到左树的最大节点
					while (max->_right) {
						max = max->_right;
					}
					cur->_key = max->_key;//将左树最大节点的值复制到当前节点
					cur->_left = deleteNode(cur->_left, cur->_key);//删除左树最大节点(左树最大节点不可能有两个孩子)
					return true;
				}
				else {//要删除地节点为叶子节点或只有一个孩子
					if (cur->_key == _root->_key) {//若要删除地节点为根节点,则更新根节点
						_root = _root->_left ? _root->_left : _root->_right;
						delete cur;
					}
					else if (cur->_left) {//否则更新父亲节点
						cur->_key > parent->_key ? parent->_right = cur->_left : parent->_left = cur->_left;
						delete cur;
					}
					else {
						cur->_key > parent->_key ? parent->_right = cur->_right : parent->_left = cur->_right;
						delete cur;
					}
					return true;
				}
			}
			if (key < cur->_key) {//寻找要删除的节点
				parent = cur;
				cur = cur->_left;
			}
			else {
				parent = cur;
				cur = cur->_right;
			}
		}
		return false;
	}

二叉搜索树的遍历

中序遍历为升序遍历,直接递归遍历即可

void _InOrder(Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}

	void InOrder() {
		_InOrder(_root);
	}

完整代码

#pragma once
#include <iostream>
#include <string>
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) {
		Node* cur = _root;
		Node* prev = _root;
		while (cur) {
			if (cur->_key == key) {//遍历树,树中已经有对应值,插入失败返回false
				return false;
			}
			if (key < cur->_key) {//当前节点的key > 要插入的key,遍历左子树
				prev = cur;
				cur = cur->_left;
			}
			else {//否则遍历右子树
				prev = cur;
				cur = cur->_right;
			}
		}
		Node* newNode = new Node(key, value);//创建新的节点
		if (!prev) {//当前树为空树,直接插入
			_root = newNode;
		}
		else {
			if (prev->_key > newNode->_key) {//插入对应位置
				prev->_left = newNode;
			}
			else {
				prev->_right = newNode;
			}
		}
		return true;
	}

	Node* Find(const K& key) {
		Node* cur = _root;
		while (cur) {
			if (key == cur->_key) {//找到对应节点,返回该节点
				return cur;
			}
			else if (key < cur->_key) {//当前节点的key > 要查找的key,到左子树中找
				cur = cur->_left;
			}
			else {//否则到右子树中找
				cur = cur->_right;
			}
		}
		return nullptr;
	}

	bool Erase(const K& key) {
		Node* cur = _root;
		Node* parent = _root;
		while (cur) {
			if (key == cur->_key) {
				//找到要删除的节点
				if (cur->_left && cur->_right) {//要删除的节点有左右两个孩子
					Node* max = cur->_left;//找到左树的最大节点
					while (max->_right) {
						max = max->_right;
					}
					cur->_key = max->_key;//将左树最大节点的值复制到当前节点
					cur->_left = deleteNode(cur->_left, cur->_key);//删除左树最大节点(左树最大节点不可能有两个孩子)
					return true;
				}
				else {//要删除地节点为叶子节点或只有一个孩子
					if (cur->_key == _root->_key) {//若要删除地节点为根节点,则更新根节点
						_root = _root->_left ? _root->_left : _root->_right;
						delete cur;
					}
					else if (cur->_left) {//否则更新父亲节点
						cur->_key > parent->_key ? parent->_right = cur->_left : parent->_left = cur->_left;
						delete cur;
					}
					else {
						cur->_key > parent->_key ? parent->_right = cur->_right : parent->_left = cur->_right;
						delete cur;
					}
					return true;
				}
			}
			if (key < cur->_key) {//寻找要删除的节点
				parent = cur;
				cur = cur->_left;
			}
			else {
				parent = cur;
				cur = cur->_right;
			}
		}
		return false;
	}
	void _InOrder(Node* root) {
		if (root == nullptr) {
			return;
		}
		_InOrder(root->_left);
		cout << root->_key << ":" << root->_value << endl;
		_InOrder(root->_right);
	}

	void InOrder() {
		_InOrder(_root);
	}
private:
	Node* _root = nullptr;
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值