【高级数据结构】B树

news2024/11/17 17:52:47

B树

  • 一、概念性问题
    • 1、前置知识:常见搜索结构
    • 2、常规使用数据结构缺陷问题
    • 3、B树概念
    • 4、存放数量分析
  • 二、代码实现逻辑
    • 1、结点定义和基本框架
    • 2、Find查找函数
      • (1)思想
      • (2)代码实现
    • 3、InsertKey插入关键字函数--InsertKey(Node* node, const K& key, Node* child)
      • (1)解析
        • i、例子一:刚开始插入第一个数情况
        • ii、例子二:叶子结点插入值但此时插入的叶子结点未满的情况
        • iii、例子三:叶子结点插入并且此时插入的叶子结点满了的情况
        • iv、例子四:非叶子节点插入情况
      • (2)代码实现
    • 4、Insert插入函数--bool Insert(const K& key)
      • (1)实现思想
        • i、第一种情况:根节点为空,插入这个结点
        • ii、第二种情况:插入当前M刚好满的情况其父亲为空
        • ii、第三种情况:插入当前M满了以后其父亲不为空
      • (2)代码实现
    • 5、中序遍历
      • (1)原理讲解
      • (2)代码实现
      • (3)展示成果
    • 6、删除(只有思路没有代码)
      • (1)当前删除值是叶子结点且值的多少是>M/2的
      • (2)当前删除值是叶子结点且值的多少是<M/2的且父亲结点数量大于M/2或兄弟结点数量大于M/2
      • (3)当前删除值是叶子结点且值的多少是<M/2的且父亲结点和兄弟结点数量小于M/2
      • (4)当前删除值是非叶子节点(数量够就借,不够就合并)


心中有B树,面试自然神~~

一、概念性问题

1、前置知识:常见搜索结构

种类数据格式 时间复杂度
顺序查找无要求 O(N)
二分查找有序 O( l o g 2 N log_2 N log2N)
二叉搜索树无要求 O(N)
二叉平衡树(AVL树和红黑树)无要求 O( l o g 2 N log_2 N log2N)
哈希无要求 O(1)

以上结构适合用于数据量相对不是很大,能够一次性存放在内存中,进行数据查找的场景。如果数据量很大,比如有100G数据,无法一次放进内存中,那就只能放在磁盘上了,如果放在磁盘上,有需要搜索某些数据,那么如果处理呢?那么我们可以考虑将存放关键字及其映射的数据的地址放到一个内存中的搜索树的节点中,那么要访问数据时,先取这个地址去磁盘访问数据。

在这里插入图片描述

2、常规使用数据结构缺陷问题

使用平衡二叉树搜索树的缺陷
平衡二叉树搜索树的高度是logN,这个查找次数在内存中是很快的。但是当数据都在磁盘中时,访问磁盘速度很慢,在数据量很大时,logN次的磁盘访问,是一个难以接受的结果。
使用哈希表的缺陷:
哈希表的效率很高是O(1),但是一些极端场景下某个位置冲突很多,导致访问次数剧增,也是难以接受的。那如何加速对数据的访问呢?1. 提高IO的速度(SSD相比传统机械硬盘快了不少,但是还是没有得到本质性的提升)2. 降低树的高度—多叉树平衡树

3、B树概念

一棵m阶(m>2)的B树,是一棵平衡的M路平衡搜索树,可以是空树或者满足一下性质:

  1. 根节点至少有两个孩子
  2. 每个分支节点都包含k-1个关键字和k个孩子,其中 ceil(m/2) ≤ k ≤ m ceil是向上取整函数。
  3. 每个叶子节点都包含k-1个关键字,其中 ceil(m/2) ≤ k ≤ m
  4. 所有的叶子节点都在同一层
  5. 每个节点中的关键字从小到大排列,节点当中k-1个元素正好是k个孩子包含的元素的值域划分
  6. 每个结点的结构为:(n,A0,K1,A1,K2,A2,… ,Kn,An)其中,Ki(1≤i≤n)为关键字,且Ki<Ki+1(1≤i≤n-1)。Ai(0≤i≤n)为指向子树根结点的指针。且Ai所指子树所有结点中的关键字均小于Ki+1。n为结点中关键字的个数,满足ceil(m/2)-1≤n≤m-1。

4、存放数量分析

这个是最满的情况:
在这里插入图片描述

最空和最满对比分析:在这里插入图片描述

二、代码实现逻辑

1、结点定义和基本框架

// B树的节点
template<class K, size_t M>
class BTreeNode
{
public:
	// 为了方便插入以后再分裂,我们多给一个空间
	K _keys[M];
	BTreeNode<K, M>* _subs[M + 1];
	BTreeNode<K, M>* _parent;
	size_t _n; // 记录实际存储了多少个关键字

	BTreeNode()
	{
		for (size_t i = 0; i < M; i++)
		{
			_keys[i] = K();
			_subs[i] = nullptr;
		}
		_subs[M] = nullptr;
		_parent = nullptr;
		_n = 0;
	}
};
// K是磁盘地址,存储在磁盘中
template<class K, size_t M>
class BTree
{
	typedef BTreeNode<K, M> Node;
public:
	// ...
private:
	Node* _root;
}

2、Find查找函数

(1)思想

我们用下面的逻辑树来进行模拟一下查找过程:
在这里插入图片描述

总共有三种情况:
第一种情况是:刚好找到的结点正好是需要返回的值,那么就返回make_pair(cur, i) // 结点和下标。
第二种情况是:比当前点的值小,那么就往它的孩子节点走,先更新parent到孩子节点,再将当前节点变成其左孩子下标即可。在这里插入图片描述
第三种情况是:比当前点的值大,关键字往后走即可。i++。

(2)代码实现

	// 寻找一个结点
	pair<Node*, int> Find(const K& key)
	{
		Node* parent = nullptr;
		Node* cur = _root; // 根节点开始
		while (cur)
		{
			// 在一个结点去查找
			size_t i = 0;
			while (i < cur->_n)
			{
				// 小于则往左子树走
				if (key < cur->_keys[i])
				{
					// 此处我们直接跳出
					break;
				}
				else if (key > cur->_keys[i])
				{
					// 往右边关键字结点走
					i++;
				}
				else
				{
					return make_pair(cur, i); // 返回当前节点和下标关系
				}
			}
			// 往孩子走
			parent = cur;
			cur = cur->_subs[i];
		}
		return make_pair(parent, -1); // 直接返回不相关的值-1即可
	}

3、InsertKey插入关键字函数–InsertKey(Node* node, const K& key, Node* child)

(1)解析

先在当前的结点进行移动,关键字比当前节点小的话就往后移动直到遇到比关键字大的那个值,插入进去,此时再改变父亲和孩子的关系,父亲的第二个值都往后移动了,那么其右孩子需要继续往后移动。
在这里插入图片描述
其实说白了这个插入关键字的结点的函数只是插入函数的分支,上面逻辑函数包含了分裂的相关知识,我们下面用几个插入例子来进行模拟一下我们插入逻辑:

i、例子一:刚开始插入第一个数情况

这个是放到insert函数中的。

		if (_root == nullptr)
		{
			_root = new Node;
			_root->_keys[0] = key;
			_root->_n++;
			return true;
		}
ii、例子二:叶子结点插入值但此时插入的叶子结点未满的情况

在这里插入图片描述

iii、例子三:叶子结点插入并且此时插入的叶子结点满了的情况

在这里插入图片描述

iv、例子四:非叶子节点插入情况

例子三和例子四可以合并再一起。

(2)代码实现

// 结点		关键字		孩子
	void InsertKey(Node* node, const K& key, Node* child)
	{
		int end = node->_n - 1; // 用end设置为最后一个元素
		while (end >= 0)
		{
			if (key < node->_keys[end]) // 关键字比当前值小
			{
				// 挪动key和它的孩子
				node->_keys[end + 1] = node->_keys[end];
				node->_subs[end + 2] = node->_subs[end + 1];
				end--;
			}
			else
			{
				break;
			}
		}
		node->_keys[end + 1] = key; // 插入关键字
		node->_subs[end + 2] = child; // 右孩子是它的孩子值
		if (child)
		{
			child->_parent = node;
		}

		node->_n++;
	}

4、Insert插入函数–bool Insert(const K& key)

(1)实现思想

i、第一种情况:根节点为空,插入这个结点
		if (_root == nullptr)
		{
			_root = new Node;
			_root->_keys[0] = key;
			_root->_n++;
			return true;
		}
ii、第二种情况:插入当前M刚好满的情况其父亲为空

在这里插入图片描述

ii、第三种情况:插入当前M满了以后其父亲不为空

在这里插入图片描述
在这里插入图片描述

(2)代码实现

	// 插入函数
	bool Insert(const K& key)
	{
		if (_root == nullptr)
		{
			_root = new Node;
			_root->_keys[0] = key;
			_root->_n++;
			return true;
		}

		// 如果key已经存在的话,不允许再插入了
		pair<Node*, int> ret = Find(key);
		if (ret.second >= 0)
		{
			return false;
		}

		// 循环每次往cur插入 newkey和child
		Node* parent = ret.first;
		K newkey = key;
		Node* child = nullptr;
		while (1)
		{
			// 先插入key值
			InsertKey(parent, newkey, child);
			// 满了就要分裂
			// 没有满,插入就结束
			if (parent->_n < M)
			{
				return true;
			}
			else
			{
				size_t mid = M / 2; // 分裂一半给兄弟
				// 分裂一半 [mid + 1, M - 1]给兄弟
				Node* brother = new Node; // 先new一个brother
				size_t j = 0;
				size_t i = mid + 1;
				for (; i <= M - 1; i++)
				{
					// 分裂拷贝key和key的左孩子
					brother->_keys[j] = parent->_keys[i];
					brother->_subs[j] = parent->_subs[i];
					
					if (parent->_subs[i])
					{
						parent->_subs[i]->_parent = brother; // 当前节点的孩子节点的父节点就为兄弟
					}
					++j;

					// 拷走重置一下方便观察
					parent->_keys[i] = K();
					parent->_subs[i] = nullptr;
				}
				// 此时还是有最后一个右孩子未被拷贝过去,拷贝到兄弟结点
				brother->_subs[j] = parent->_subs[i]; // 兄弟的孩子节点就是当前节点的孩子节点
				if (parent->_subs[i])
				{
					parent->_subs[i]->_parent = brother;
				}
				parent->_subs[i] = nullptr;

				brother->_n = j; // 拷贝过去的数量
				parent->_n -= (brother->_n + 1); // 当前节点减少的数量

				K midKey = parent->_keys[mid]; 
				parent->_keys[mid] = K();

				// 假如说是刚刚分裂的是根节点
				if (parent->_parent == nullptr)
				{
					_root = new Node;
					_root->_keys[0] = midKey; // 中间结点
					_root->_subs[0] = parent;
					_root->_subs[1] = brother;
					_root->_n = 1;

					parent->_parent = _root;
					brother->_parent = _root;
					break;
				}
				else
				{
					newkey = midKey;

					child = brother;
					parent = parent->_parent;
				}
			}
		}
		return true;
	}

5、中序遍历

(1)原理讲解

(2)代码实现

	// 中序遍历子函数
	void _OrderPrint(Node* root)
	{
		if (root == nullptr)
			return;
		size_t i = 0;
		for (; i < root->_n; i++)
		{
			// 往左边走
			//root = root->_subs[i];
			_OrderPrint(root->_subs[i]);
			std::cout << root->_keys[i] << " ";
		}
		// 往最后一个右子树走
		_OrderPrint(root->_subs[i]);
	}
	// 中序遍历
	void OrderPrint()
	{
		return _OrderPrint(_root);
	}

(3)展示成果

打印的完全没毛病!
在这里插入图片描述

6、删除(只有思路没有代码)

记住口诀:父亲往他的父亲借是肯定能给的,而往小儿子借不一定给(小儿子数量小于M/2)!

(1)当前删除值是叶子结点且值的多少是>M/2的

在这里插入图片描述

(2)当前删除值是叶子结点且值的多少是<M/2的且父亲结点数量大于M/2或兄弟结点数量大于M/2

在这里插入图片描述

(3)当前删除值是叶子结点且值的多少是<M/2的且父亲结点和兄弟结点数量小于M/2

在这里插入图片描述

(4)当前删除值是非叶子节点(数量够就借,不够就合并)

图的实现这里不放了,有点多…

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

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

相关文章

采用伪代码及C代码演示如何解决脱机最小值问题

采用伪代码及C代码演示如何解决脱机最小值问题 问题背景算法设计伪代码实现C代码实现证明数组正确性使用不相交集合数据结构最坏情况运行时间的紧确界 问题背景 脱机最小值问题涉及到一个动态集合 &#xff08; T &#xff09; &#xff08;T&#xff09; &#xff08;T&…

国内大模型价格战全面爆发:新旧势力逐鹿江湖【附主流模型价格对比】

近年来&#xff0c;随着人工智能技术的不断发展&#xff0c;大模型逐渐成为行业的焦点。然而&#xff0c;伴随而来的却是一场价格战。DeepSeek率先推出超低价服务&#xff0c;随后字节跳动、阿里巴巴、百度、科大讯飞、腾讯等巨头纷纷跟进&#xff0c;使得这一领域的竞争愈演愈…

echarts-树图、关系图、桑基图、日历图

树图 树图主要用来表达关系结构。 树图的端点也收symbol的调节 树图的特有属性&#xff1a; 树图的方向&#xff1a; layout、orient子节点收起展开&#xff1a;initialTreeDepth、expandAndCollapse叶子节点设置&#xff1a; leaves操作设置&#xff1a;roam线条&#xff1a…

Mysql触发器优化大数据表

背景 数据库的订单数量过多&#xff0c;需要分出热表用于快速查询&#xff0c;热表仅保存10天的订单数据。 解决思路 每次数据库订单表触发增删改时&#xff0c;同步操作到trigger_order_mul_info表&#xff0c;然后trigger_order_mul_info会定期删除超过10天的数据。 增删…

【编译原理复习笔记】正则表达式与自动机

正则表达式 正则表达式是一种用来描述正则语言的更紧凑的表达方法 e.g. r a ( a ∣ b ) ∗ ( ϵ ∣ ( . ∣ ) ( a ∣ b ) ) ra(a|b)^*(\epsilon|(.|\\_ )(a|b)) ra(a∣b)∗(ϵ∣(.∣)​(a∣b)) 正则表达式可以由较小的正则表达式按照特定的规则递归地构建。每个正则表达式定义…

【笔记】软件架构师要点记录(1)

【笔记】软件架构师要点记录 20240517 20240517 连续性&#xff1a;恢复能力&#xff1b;可用性&#xff1a;保持稳定态的时长 增量开发模式&#xff1a;在增量开发中&#xff0c;每个增量都有明确的范围和功能&#xff0c;并按照特定的功能顺序完成。增量之间的范围划分在开发…

防火墙技术基础篇:基于IP地址的转发策略

防火墙技术基础篇&#xff1a;基于IP地址的转发策略的应用场景及实现 什么是基于IP地址的转发策略&#xff1f; 基于IP地址的转发策略是一种网络管理方法&#xff0c;它允许根据目标IP地址来选择数据包的转发路径。这种策略比传统的基于目的地地址的路由更灵活&#xff0c;因…

图片转excel技术在医疗领域的应用探讨

在医疗行业中&#xff0c;图片转Excel技术的应用已经逐渐普及&#xff0c;为医护人员提供了极大的便利。这种技术利用OCR&#xff08;光学字符识别&#xff09;和机器学习的先进算法&#xff0c;将图片中的信息自动转化为Excel表格&#xff0c;大大提高了数据处理和分析的效率。…

智能锁千千万,谁是你的NO.1,亲身实测凯迪仕传奇大师K70旗舰新品

智能锁千千万&#xff0c;谁是你的NO.1。欢迎来到智哪儿评测室&#xff0c;这次我们为大家带来了凯迪仕传奇大师K70系列的一款重磅新品。 在科技的浪潮中&#xff0c;家居安全领域正经历着前所未有的变革。智能锁越来越成为家的安全守护神&#xff0c;以及智能生活的得力助手。…

Monodle centerNet3D 瑞芯微RKNN、地平线Horizon芯片部署、TensorRT部署

一直想做一点3D目标检测&#xff0c;先来一篇单目3D目标检测Monodle&#xff08;基于centernet的&#xff09;&#xff0c;训练代码参考官方【代码】&#xff0c;这里只讲讲如何部署。 模型和完整仿真测试代码&#xff0c;放在github上参考链接【模型和完整代码】。 1 模型训练…

Creating Server TCP listening socket *:6379: listen: Unknown error

错误&#xff1a; 解决方法&#xff1a; 在redis安装路径中打开cmd命令行窗口&#xff0c;输入 E:\Redis-x64-3.2.100>redis-server ./redis.windows.conf结果&#xff1a;

智慧校园学工管理系统的部署

学工体系思政服务该怎么规划建造&#xff1f;思政作为高校育人的中心使命&#xff0c;在做到让学生健康高兴生长的一起&#xff0c;也应满意学生生长成才的各类需求。使用技术为学生供给优质的信息化服务&#xff0c;是其间的有效途径。大数据让个性化教育成为可能&#xff0c;…

Python函数、类和方法

大家好&#xff0c;当涉及到编写可维护、可扩展且易于测试的代码时&#xff0c;Python提供了一些强大的工具和概念&#xff0c;其中包括函数、类和方法。这些是Python编程中的核心要素&#xff0c;可以帮助我们构建高效的测试框架和可靠的测试用例。 本文将探讨Python中的函数、…

Swin Transformer 笔记与理解

目录 解决什么问题基本结构理解 解决什么问题 传统的transformer处理于长序列需要非常大的计算量&#xff0c;而且很慢。且传统的transformer虽然的全局信息的获取上有着很好的效果&#xff0c;但是在局部信息的获取上就没有那么强了。Swim transformer的主要的贡献就是使用分…

LLM 大模型学习必知必会系列(十):基于AgentFabric实现交互式智能体应用,Agent实战

LLM 大模型学习必知必会系列(十)&#xff1a;基于AgentFabric实现交互式智能体应用,Agent实战 0.前言 **Modelscope **是一个交互式智能体应用基于ModelScope-Agent&#xff0c;用于方便地创建针对各种现实应用量身定制智能体&#xff0c;目前已经在生产级别落地。AgentFabri…

Java输入与输出详解

Java输入和输出 前言一、Java打印Hello World二、输出到控制台基本语法代码示例格式化字符串 三、从键盘输入读入一个字符正确写法 使用 Scanner 读取字符串/整数/浮点数使用 Scanner 循环读取 N 个数字 前言 推荐一个网站给想要了解或者学习人工智能知识的读者&#xff0c;这…

嵌入式智能硬件茶杯垫的设计与实现方案

iCupBox简介 这是一款智能杯垫产品,基于GTD时间管理理念设计,目的是提醒人们专心工作和及时喝水休息,提高工作效率。 https://gitee.com/jiangtao008/iCupBox 开原许可协议:MIT 项目分为客户端APP和杯垫固件系统: 客户端APP,使用QML开发,集成GTD时间管理方法,与杯垫固…

QQ技术导航源码附带交易系统

网站功能 QQ登录 友联自助交换 友情链接交易功能 多功能搜索 ico小图标本地化 网站图片本地化 蜘蛛日志 文章评论 网站评论 自助链接匿名提交站点&#xff0c;添加友链访问网站自动审核通过 VIP 会员等级 VIP 付费升级 单个文章或者站点付费快审 多背景图片可自定义背景图片…

Web Server项目实战2-Linux上的五种IO模型

上一节内容的补充&#xff1a;I/O多路复用是同步的&#xff0c;只有调用某些API才是异步的 Unix/Linux上的五种IO模型 a.阻塞 blocking 调用者调用了某个函数&#xff0c;等待这个函数返回&#xff0c;期间什么也不做&#xff0c;不停地去检查这个函数有没有返回&#xff0c…

【算法】二分算法——寻找峰值

题解&#xff1a;寻找峰值(二分算法) 目录 1.题目2.暴力求解3.二分算法4.总结 1.题目 题目链接&#xff1a;LINK 2.暴力求解 暴力求解的思路很简单&#xff0c;这个数组的形状无非就三种&#xff1a; 一直上升下降(这里包含先下降后上升)先升后降 总结一下规律&#xff1…