C++二叉搜索树与KV模型

news2024/10/7 16:15:48

二叉搜索树与KV模型

  • 二叉搜索树
    • 概念与操作
    • 性能分析
    • 实现
    • KV模型

二叉搜索树

本章是为了C++的map和set做铺垫

概念与操作

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

int a[] = {8, 3, 1, 10, 6, 4, 7, 14, 13};

二叉搜索树的查找

a、从根开始比较,查找,比根大则往右边走查找,比根小则往左边走查找。
b、最多查找高度次,走到到空,还没找到,这个值不存在。

二叉搜索树的插入

a. 树为空,则直接新增节点,赋值给root指针。
b. 树不空,按二叉搜索树性质查找插入位置,插入新节点。

其实上面两个还是很容易实现的,最难的是删除这里,要考虑三种情况:
删除的是叶子结点,那就找到直接释放就好了。
删除的是只有一个左/右孩子的结点,那就先链接父亲结点和左/右孩子结点,然后直接删除该节点。
其实删除叶子结点可以和删除一个孩子结点的合并,因为叶子节点两个都是空,删除一个孩子的结点只有一个是空。
删除两个孩子结点的最麻烦,首先要找到替换这个结点的值,肯定是左子树最大的或者是右子树最小的。(二选一都可以,这里我选择左子树最小的)

性能分析

对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索树平均查找长度是结点在二
叉搜索树的深度的函数,即结点越深,则比较次数越多。
但对于同一个关键码集合,如果各关键码插入的次序不同,可能得到不同结构的二叉搜索树:
在这里插入图片描述
最优情况下,二叉搜索树为完全二叉树(或者接近完全二叉树),其平均比较次数为: l o g 2 N log_2 N log2N
最差情况下,二叉搜索树退化为单支树(或者类似单支),其平均比较次数为: N 2 \frac{N}{2} 2N
其实二叉搜索树是一个不完整的树,遇到这种极端情况就没有办了,后面的AVL和红黑树会完成这个功能。

实现

实现这里我会写出某些成员函数的递归与迭代版本。
其实最难的是删除步骤:
在这里插入图片描述
如果是删除叶子结点直接删除就可以,只有一个孩子的就先看是只有左孩子还是只右有孩子,如果只有左孩子就让父节点的指针指向左孩子,如果只有右孩子就让父亲的指针指向右孩子,然后判断要删除的结点在父节点的左子树还是右子树,才能判断让父节点的左指针还是右指针去链接孩子。
但是这个思路还要考虑这种情况:
在这里插入图片描述
如果删除了根节点,那么就要换根。
在这里插入图片描述

如果是有两个孩子的就非常难办了,首先要去替换,这里用左子树的最小节点去替换。
先来看看第一种情况
在这里插入图片描述
删除3,首先让3和左子树的最小值4交换(赋值也行),然后让cur记住原来是3这里,再来一个指针记录原来是4这里,再用parent指向父节点6的位置:
在这里插入图片描述
然后删除minright之前,将parent的左指针指向minright的右指针,因为minright左指针肯定是没有值了,但是右指针不一定没有。
然后是第二种情况
删除的是根,这里只能和10交换,parent就是根,10就是minright,释放minright之前要将parent的右与minright的右链接起来。

迭代

#include<iostream>
#include<vector>
using namespace std;
template<class K>
struct BSTNode//树结点
{
	BSTNode(K key)
		:_key(key)
		,left(nullptr)
		,right(nullptr)
	{}
	K _key;//结点值
	BSTNode<K>* left;//左子树
	BSTNode<K>* right;//右子树
};
template<class K>
class BSTree//树
{
	typedef BSTNode<K> Node;
public:
	BSTree()
		:_root(nullptr)
	{}

	bool Insert(const K& key)//插入数据
	{

		if (_root == nullptr)
		{
			_root = new Node(key);
			return true;
		}
		Node* cur = _root;
		Node* parent = cur;
		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->left = cur;
		else
			parent->right = cur;
		return true;
	}
	bool Find(const K& key)//查找
	{
		Node* cur = _root;
		if (cur == nullptr)
		{
			return false;
		}
		while (cur)
		{
			if (cur->_key > key)
				cur = cur->left;
			else if (cur ->_key < key)
				cur = cur->right;
			else
			{
				return true;
			}
		}
		return false;
	}
	bool Erase(const K& key)//删除数据
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		Node* parent = cur;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
			{
				if (cur->left == nullptr)//只有右孩子
				{
					if (cur == _root)
					{
						_root = cur->right;
					}
					else if (parent->left == cur)
					{
						parent->left = cur->right;
					}
					else
					{
						parent->right = cur->right;
					}
					delete cur;//释放结点
				}
				else if (cur->right == nullptr)//只有左孩子
				{
					if (cur == _root)
					{
						_root = cur->left;
					}
					else if (parent->left == cur)
					{
						parent->left = cur->left;
					}
					else
					{
						parent->right = cur->left;
					}
					delete cur;//释放结点
				}
				else//有两个孩子
				{
					Node* minright = cur->right;
					parent = cur;
					while (minright->left)//找到右子树最小值
					{
						parent = minright;
						minright = minright->left;
					}
					//赋值
					cur->_key = minright->_key;
					//链接
					if (parent->left == minright)
						parent->left = minright->right;
					else
						parent->right = minright->right;
					delete minright;
				}
				
				return true;
			}
		}
		return false;
	}
	void _InOrder()//因为不想让外界访问道内部的_root,所以只能通过成员函数内部访问
	{
		InOrder(_root);
		cout << endl;
	}
private:
	void InOrder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		InOrder(root->left);
		cout << root->_key << ' ';
		InOrder(root->right);
	}

	Node* _root;//树的根结点
};

递归
递归的函数都要带头结点,也就是说又要去调用子函数的方式来调用对应的递归函数。
查找

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

递归这里有些地方很巧妙:
插入

bool InsertR(Node*& root, const K& key)//这里的root用了引用
{
	if (_root == nullptr)
	{
		_root = new Node(key);
		return true;
	}
	if (_root->_key < key)
		InsertR(_root->right, key);
	else if (_root->_key > key)
		InsertR(_root->left, key);
	else
		return false;
}

为什么这里的头结点用了引用呢?是因为解决链接问题的:
在这里插入图片描述
假设这里插入了一个6,应该是在5的右孩子那里,当我们找到位置的时候,上一层传递的是5的右指针,用了引用就是5的右指针的别名,开辟空间之后直接让5的右指针就能指向这块空间。
删除

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* cur = root;
		if (root->right == nullptr)//只有左孩子
		{
			root = root->left;//这里不仅仅找到的是key,也是父节点指向该节点的指针别名
		}
		else if (root->left == nullptr)//只有右孩子
		{
			root = root->right;//这里也不用担心删除的是根,直接就将_root指向了下一个结点
		}
		else//有两个孩子,这里的引用root就不管用了,因为引用不能改指向,在这里往下找右树最小值是行不通的
		{
			Node* parent = root->right;//去该节点的右子树查找最小值
			while (parent->left)
			{
				parent = parent->left;
			}
			swap(parent->_key, root->_key);//交换两个值
			return EraseR(root->right, key);//再次去找该节点最小值,也就是被交换的值,然后进行删除
		}
		delete cur;
		return true;
	}
	return false;
}

在这里插入图片描述
析构

	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* cur = new Node(root->_key);
	cur->left = Copy(root->left);
	cur->right = Copy(root->right);
	return cur;//这里将新开辟的结点返回给上一层,让上一层的指针指向这里,就能连接起来
}

KV模型

KV模型前身还有一个K(key)模型,就是上面的搜索二叉树,比如说要检查一个英语单词写的对不对,就要创建一个词库(搜索二叉树)里面找。
KV(key/value)模型是我们去查找一个英语单词的汉译,不可能在庞大的库中一个一个寻找词汇,而是通过搜索二叉树的形式寻找,那么一个单词相对应一个汉译,这个模型叫做KV模型。
其实本质就是一个结点有两个值而已。
这里的查找就很有用了,举个例子,一个学生来图书馆借书,借了多少本书需要知道,这本书归还没有也需要知道,所以这里查找也顺便进行修改。

#include<iostream>
#include<vector>
#include<string>
using namespace std;
template<class K, class V>
struct BSTNode//树结点
{
	BSTNode(const K& key, const V& value)
		:_key(key)
		,_value(value)
		, left(nullptr)
		, right(nullptr)
	{}
	K _key;
	V _value;
	BSTNode<K, V>* left;//左子树
	BSTNode<K, V>* right;//右子树
};
template<class K, class V>
class BSTree//树
{
	typedef BSTNode<K, V> Node;
public:
	BSTree()
		:_root(nullptr)
	{}
	~BSTree()
	{
		Destroy(_root);
		_root = nullptr;
	}
	bool Insert(const K& key, const V& value)//插入数据
	{
		if (_root == nullptr)
		{
			_root = new Node(key, value);
			return true;
		}
		Node* cur = _root;
		Node* parent = cur;
		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, value);
		if (parent->_key > key)
			parent->left = cur;
		else
			parent->right = cur;
		return true;
	}
	Node* Find(const K& key)//查找
	{
		Node* cur = _root;
		if (cur == nullptr)
		{
			return nullptr;
		}
		while (cur)
		{
			if (cur->_key > key)
				cur = cur->left;
			else if (cur->_key < key)
				cur = cur->right;
			else
			{
				return cur;//找到就返回该节点
			}
		}
		return nullptr;
	}
	bool Erase(const K& key)//删除数据
	{
		if (_root == nullptr)
		{
			return false;
		}
		Node* cur = _root;
		Node* parent = cur;
		while (cur)
		{
			if (cur->_key < key)
			{
				parent = cur;
				cur = cur->right;
			}
			else if (cur->_key > key)
			{
				parent = cur;
				cur = cur->left;
			}
			else
			{
				if (cur->left == nullptr)//只有右孩子
				{
					if (cur == _root)
					{
						_root = cur->right;
					}
					else if (parent->left == cur)
					{
						parent->left = cur->right;
					}
					else
					{
						parent->right = cur->right;
					}
					delete cur;//释放结点
				}
				else if (cur->right == nullptr)//只有左孩子
				{
					if (cur == _root)
					{
						_root = cur->left;
					}
					else if (parent->left == cur)
					{
						parent->left = cur->left;
					}
					else
					{
						parent->right = cur->left;
					}
					delete cur;//释放结点
				}
				else//有两个孩子
				{
					Node* minright = cur->right;
					parent = cur;
					while (minright->left)//找到右子树最小值
					{
						parent = minright;
						minright = minright->left;
					}
					//赋值
					cur->_key = minright->_key;
					//链接
					if (parent->left == minright)
						parent->left = minright->right;
					else
						parent->right = minright->right;
					delete minright;
				}

				return true;
			}
		}
	}
	void _InOrder()//因为不想让外界访问道内部的_root,所以只能通过成员函数内部访问
	{
		InOrder(_root);
		cout << endl;
	}
	bool _FindR(const K& key)
	{
		return FindR(_root, key);
	}
private:
	void Destroy(Node* root)
	{
		if (root == nullptr)
			return;
		Destroy(root->left);
		Destroy(root->right);
		delete root;
	}
	void InOrder(Node* root)//中序遍历
	{
		if (root == nullptr)
		{
			return;
		}
		InOrder(root->left);
		cout << root->_key << ' ';
		cout << root->_value << ' ';
		InOrder(root->right);
	}

	Node* _root;//树的根结点
};

void test()
{
	BSTree<string, string> tree;
	tree.Insert("1", "one");
	tree.Insert("2", "two");
	tree.Insert("3", "three");
	tree.Insert("4", "four");
	tree.Insert("5", "five");
	BSTNode<string, string>* ret;//储存返回查找到结点的值
	string str;
	while (cin >> str)
	{
		ret = tree.Find(str);
		if (ret)
			cout << ret->_value << endl;
		else
			cout << "没有此结果" << endl;
	}
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/415940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面试题之vue的响应式

文章目录前言一、响应式是什么&#xff1f;二、Object.defineProperty二、简单模拟vue三、深度监听四、监听数组总结前言 为了应对面试而进行的学习记录&#xff0c;可能不够有深度甚至有错误&#xff0c;还请各位谅解&#xff0c;并不吝赐教&#xff0c;共同进步。 一、响应式…

如何做好 IT 项目管理?做好项目管理常用的9大项目管理平台、7大管理方法

一个好的管理&#xff0c;是70%在流程、规范、工具&#xff0c;剩下的30%自由发挥。一个不好的管理&#xff0c;只有地板&#xff0c;每个人都要自己想办法&#xff0c;够到天花板。一个好的工具&#xff0c;就是帮助团队够到天花板的台阶。——刘润 项目管理是一门复杂的艺术&…

统一的文件管理,团队轻松协作

目前IT行业大都采用项目经理制的管理方式&#xff0c;这种管理方式下各个部门间相互独立&#xff0c;同时各部门间也缺乏沟通协作。因此IT行业在文件管理上主要面临以下几个问题&#xff1a; 文档缺乏集中管理&#xff1a;企业在管理过程中产生的大量文件分散在各个部门中&…

Python升级 pip : python -m pip install --upgrade pip,socket.timeout加入超时处理方法

人生苦短&#xff0c;我用python 最近又遇到了一个小的报错问题&#xff0c; 趁现在我还没有忘记&#xff0c; 赶紧来写一写… python 安装包资料报错交流:点击此处跳转文末名片获取 WARNING: You are using pip version 19.3.1; however, version 20.0.2 is available. You…

系统学习Numpy(一)——numpy的安装与基础入门[向量、矩阵]

系列文章目录 numpy的安装与基础入门[向量、矩阵与维度] numpy的安装与基础入门[向量、矩阵与维度]系列文章目录前言numpy安装向量与矩阵生成向量生成矩阵向量类型前言 numpy是科学计算以及机器学习深度学习的基础必备工具&#xff0c;本文将介绍numpy的安装&#xff0c;以及…

C语言课设项目-51单片机-中断系统

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 51单片机的中断系统 一、中断的概念 二、51单片机的中断系统结构 三、中断允许控制 四、中断…

C#,初学琼林(06)——组合数的算法、数据溢出问题的解决方法及相关C#源代码

1 排列permutation 排列&#xff0c;一般地&#xff0c;从n个不同元素中取出m&#xff08;m≤n&#xff09;个元素&#xff0c;按照一定的顺序排成一列&#xff0c;叫做从n个元素中取出m个元素的一个排列(permutation)。特别地&#xff0c;当mn时&#xff0c;这个排列被称作全…

vs code c语言断点调试window版解决方案

序&#xff1a; 1、这一步不懂劝退多少人&#xff0c;博主搜到了多少博文都是mac的&#xff0c;结果发现都对不上&#xff01; 先看最终效果演示 接下去我每个步骤&#xff0c;你都仔细看&#xff0c;漏看一个环境都对不上&#xff01; 正文 1、先去看博主的c/c运行环境配置图…

10-vue3动画

文章目录1.vue的transition动画1.1transition的基本使用1.2transition组件的原理1.3过渡动画的class1.4class的命名规则和添加时机1.5显示的指定过渡时间1.6过渡的模式mode1.7动态组件的切换1.8.appear初次渲染2、animation动画2.1同时设置animation和transition3.结合第三方库…

【Bard】来自谷歌的“吟游诗人”

个人主页&#xff1a;【&#x1f60a;个人主页】 文章目录前言Bard与相关产品的对比Bard VS 弱智吧来自对手的评论ChatGPT文心一言总结&#xff1a;前言 相比较ChatGPT的话题不断&#xff0c;谷歌的“Bard”显然低调了许多&#xff0c;在“画大饼”失败一个多月后&#xff0c…

【Python开发手册】深入剖析Google Python开发规范:规范Python注释写作

&#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;全栈领域优质创作者。&#x1f4dd; CSDN主页&#xff1a;Zeeland&#x1f525;&#x1f4e3; 我的博客&#xff1a;Zeeland&#x1f4da; Github主页: Undertone0809 (Zeeland) (github.com)&…

高通开发系列 - linux kernel内核升级msm-4.9升级至msm-4.19(2)

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 MDSS PLL驱动问题msm-4.19内核适配nand flashMDSS PLL驱动问题 | /home/peeta/sc262R_private_rl/build-msm8909/tmp/work-shared/ms…

第01章_Java语言概述

第01章_Java语言概述 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 1. Java知识脉络图 1.1 Java基础全程脉络图 1.2 本章专题与脉络 2. 抽丝剥茧话Java 2.1 当前大学生就业形势 麦可思研究院…

C++之红黑树

文章目录前言一、概念二、性质三、结点的定义四、红黑树的结构五、插入操作1.插入代码2.左单旋3.右单旋4.插入新结点的情况分析与总结第一步、按照搜索二叉树的规则插入新结点第二步、分析插入结点后红黑树的性质是否被破坏动态演示&#xff1a;六、验证红黑树1.检测是否满足二…

口令暴力破解--Telnet协议暴力破解、数据库暴力破解与远程桌面暴力破解

Telnet协议暴力破解 Telnet Telnet协议是TCP/IP协议族中的一员&#xff0c;是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。要开始一个telnet会话&#xff0c;必须输入用户名和密码来登录服务器。而一般服务器不会对用户名…

【数据结构】6.4 AVL树(C++)

【数据结构】——6.4 AVL树 没有学过二叉搜索树&#xff08;也叫二叉排序树或二叉查找树&#xff09;的小伙伴们建议先学习一下&#xff0c;这样阅读会更轻松哦 点我学习二叉搜索树 目录一、AVL树的概念1. 二叉搜索树的问题2. AVL树的性质二、AVL树实现平衡的方法1. 更新平衡因…

【音视频第11天】GCC论文阅读(2)

A Google Congestion Control Algorithm for Real-Time Communication draft-alvestrand-rmcat-congestion-03论文理解 看中文的GCC算法一脸懵。看一看英文版的&#xff0c;找一找感觉。 目录Abstract1. Introduction1.1 Mathematical notation conventions2. System model3.Fe…

Shader 海面/水面

首先用Terrain在场景中随便做个地形&#xff0c;当作海底 上面加个Plane作为海面 实现海水效果要考虑海水深度对颜色的影响&#xff0c;法线移动形成波浪&#xff0c;菲涅尔&#xff0c;高光等效果 深度 海水深的地方颜色深&#xff0c;浅的地方颜色浅&#xff0c;所以海边和…

fastDFS文件管理系统在linux下部署

1.概述 fastDFS分布式文件系统包括三个中要部分&#xff1a;追踪器、存储节点、客户端&#xff0c;可以使用文件存储&#xff0c;文件同步&#xff0c;文件访问等功能&#xff0c;用来存储大容量数据 存储节点集群&#xff1a; 横向扩容&#xff1a;增加存储容量 纵向扩容&…

liunx系统(VMware Workstation Pro)详细安装配置docker

​ 安装东西前要知道docker是什么,以及docker能都干什么,文章都是本人亲测然后写的过程. http://t.csdn.cn/iqbGg 博客文章链接详细介绍docker,以及部署MySQL,nginx等配置 一. liunx系统(VMware) 安装Docker 1. Docker中文网地址: Docker中文网 官网 (p2hp.com) 2. 打开VM…