[C++][数据结构][B-树][下]详细讲解

目录

  • 1.B-树的实现
    • 1.B-树的结点设计
    • 2.插入key的过程
    • 3.B-树的插入实现
    • 4.B-树的简单验证
    • 5.B-树的性能分析
    • 6.B树的删除
  • 2.B+树
  • 3.B*树
  • 4.B-树总结
  • 5.B-树的应用
    • 0.B树可以在内存中做内查找吗?
    • 1.索引
    • 2.MYSQL索引简介
      • 1.MyISAM
      • 2.InnoDB
    • 3.B+树做主键索引相比B树的优势


1.B-树的实现

1.B-树的结点设计

template<class K, size_t M>
struct BTreeNode
{
	// 为了方便插入以后再分裂,多给一个空间
	K _keys[M];
	BTreeNode<K, M>* _subs[M + 1];     

	BTreeNode<K, M>* _parent = nullptr;;
	size_t _n = 0; // 记录实际存储关键字的个数

	BTreeNode()
	{
		for (size_t i = 0; i < M; i++)
		{
			_keys[i] = K();
			_subs[i] = nullptr;
		}

		_subs[M] = nullptr;
	}
};

2.插入key的过程

  • 按照插入排序的思想插入key
    • 注意:在插入key的同时,可能还要插入新分裂出来的节点
void InsertKey(Node* node, const K& key, Node* child)
{
	// 直接插入排序
	int end = node->_n - 1;

	while (end >= 0)
	{
		if (key < node->_keys[end])
		{
			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++;
}

3.B-树的插入实现

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

	// 如果没有找到,Find()顺便带回了要插入的那个叶子结点
	// 循环每次往cur插入,newkey和child
	Node* parent = ret.first;
	K newKey = key;
	Node* child = nullptr;

	while (1)
	{
		InsertKey(parent, newKey, child);

		// 没有满,插入就结束
		if (parent->_n < M)
		{
			return true;
		}
		
		// 满了就要分裂
		// 分裂一般[mid + 1, M - 1]给兄弟
		Node* bro = new Node;
		size_t mid = M / 2;
		size_t i = mid + 1;
		size_t j = 0;
		
		for (; i < M; i++)
		{
			// 分裂拷贝key和key的左孩子
			bro->_keys[j] = parent->_keys[i];
			bro->_subs[j++] = parent->_subs[i];
			
			// 更新parent->_keys[i]父节点为bro
			if (parent->_keys[i])
			{
				parent->_keys[i]->_parent = bro;
			}

			// 拷走之后,重置为默认值,方便观察
			parent->_keys[i] = K();
			parent->_subs[i] = nullptr;
		}

		// 还有最后一个最右孩子
		bro->_subs[j] = parent->_subs[i];
		if (parent->_keys[i])
		{
			parent->_keys[i]->_parent = bro;
		}
		parent->_subs[i] = nullptr;

		bro->_n = j;
		parent->_n -= (bro->_n + 1);

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

		// 说明刚刚分裂的是根节点
		if (parent->_parent == nullptr)
		{
			_root = new Node;
			_root->_keys[0] = midkey;
			_root->_subs[0] = parent;
			_root->_subs[1] - bro;
			_root->_n = 1;

			parent->_parent = _root;
			bro->_parent = _root;
			break;
		}
		else
		{
			// 转换成往parent->_parent中去插入midKey和bro
			newKey = midkey;
			child = bro;
			parent = parent->_parent;
		}
	}

	return true;
}

4.B-树的简单验证

  • 对B树进行中序遍历,如果能得到一个有序的序列,说明插入正确
	void _InOrder(Node* cur)
	{
		if (cur == nullptr)
		{
			return;
		}

		// 左 根 左 根 ... 右
		for (size_t i = 0; i < M; i++)
		{
			_InOrder(cur->_subs[i]); // 左子树
			cout << cur->_keys[i] << " "; // 根
		}
		_InOrder(cur->_subs[i]); // 最右子树
	}

	void InOrder()
	{
		_InOrder(_root);
	}

5.B-树的性能分析

  • 对于一棵结点为N,度为M的B-树,查找和插入需要 l o g ( M − 1 ) N log_{(M-1)}N log(M1)N~ l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N次比较
    • 对于度为M的B-树,每一个节点的子节点个数为 M / 2 − ( M − 1 ) M/2 - (M-1) M/2(M1)之间
    • 因此树的高度应该在要 l o g ( M − 1 ) N log_{(M-1)}N log(M1)N l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N之间
    • 在定位到该节点后,再采用二分查找的方式可以很快的定位到该元素
  • B-树的效率是很高的,对于N = 620亿个节点,如果度M为1024,则 l o g ( M / 2 ) N log_{(M/2)}N log(M/2)N <= 4
    • 即:在620亿个元素中,如果这棵树的度为1024,则需要小于4次即可定位到该节点,然后利用二分查找可以快速定位到该元素,大大减少了读取磁盘的次数

6.B树的删除

  • 学习B树的插入足够帮助理解B树的特性了
  • 大致思路:
    • 节点数量小于 m / 2 − 1 m/2-1 m/21,则优先找父亲借,父亲找兄弟借
    • 若找父亲和兄弟借不到节点了,再借它们也不满足条件 m / 2 − 1 m/2 - 1 m/21
      • 合并兄弟节点
  • 若对删除有兴趣,可以参考参考
    • 《算法导论》-- 伪代码
    • 《数据结构-殷人昆》-- C++实现代码

2.B+树

  • B+树是B树的变形,是在B树基础上优化的多路平衡搜索树
  • B+树的规则跟B树基本类似,但又在B树的基础上做了以下几点改进优化:
    • 分支结点的子树指针与关键字个数相同
      • 相当于取消了最左边的那个子树
    • 分支结点的子树指针 p [ i ] p[i] p[i]指向关键字值大小在 ( [ k [ i ] , k [ i + 1 ] ) ([k[i],k[i+1]) ([k[i]k[i+1])区间之间
      • 分支结点跟叶子结点有重复的值,分支结点存的是叶子节点索引
      • 父亲中存的是孩子节点中的最小值的索引
    • 所有叶子节点增加一个链接指针链接在一起
    • 所有关键字及其映射数据都在叶子节点出现
  • B+树的特性:
    • 所有关键字都出现在叶子节点的链表中,且链表中的节点都是有序的
    • 不可能在分支结点中命中
    • 分支节点相当于是叶子节点的索引,叶子节点才是存储数据的数据层
  • B+树的分裂:
    • 第一次插入两层节点,一层做根,一层做分支
      • 后面跟B树一样,往叶子去插入
    • 当一个结点满时,分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针
    • B+树的分裂只影响原结点和父结点,而不会影响兄弟结点,所以它不需要指向兄弟的指针
  • B+ vs B
    • 分支结点只存储key,分支结点比较小
    • 分支结点映射的磁盘数据块就可以尽量加载到Cache
  • 总结:
    • 简化B树孩子比关键字多一个的规则,变成相等

    • 所有值都在叶子上,方便遍历查找所有值

      请添加图片描述


3.B*树

  • B*树是B+树的变形,在B+树的非根和非叶子节点再增加指向兄弟节点的指针
  • B*树的结点关键字和孩子数量 --> [ 2 / 3 ∗ M , M ] [2/3*M, M] [2/3M,M]
  • B*树的分裂:当一个结点满时
    • 如果它的下一个兄弟结点未满,那么将一部分数据移到兄弟结点中,再在原结点插入关键字,最后修改父结点中兄弟结点的关键字(因为兄弟结点的关键字范围改变了)
    • 如果兄弟也满了,则在原结点与兄弟结点之间增加新结点,并各复制1/3的数据到新结点,最后在父结点增加新结点的指针
  • 所以,B*树分配新结点的概率比B+树要低,空间使用率更高

请添加图片描述


4.B-树总结

  • **B树:**有序数组+平衡多叉树
  • **B+树:**有序数组链表+平衡多叉树
  • **B*树:**一棵更丰满的,空间利用率更高的B+树

5.B-树的应用

0.B树可以在内存中做内查找吗?

  • 可以,但不合适
  • B树系列和哈希&平衡搜索树对比:
    • 单纯轮树高度、搜索效率而言,B树确实不错
    • 但是B树系列有一些隐形缺点
      • 空间利用率低,消耗高
      • 插入删除数据时,分裂和合并节点,那么必然挪动数据
      • 虽然高度更低,都是在内存中而言,跟哈希和平衡搜索树还是一个量级
  • 结论:实质上B树系列再内存中体现不出优势

1.索引

  • B-树最常见的应用就是用来做索引
  • 索引通俗的说就是为了方便用户快速找到所寻之物,比如:
    • 书籍目录可以让读者快速找到相关信息
    • 网页导航网站,为了让用户能够快速的找到有价值的分类网站,本质上就是互联网页面中的索引结构
  • MySQL官方对索引的定义为:
    • 索引(index)是帮助MySQL高效获取数据的数据结构
    • 简单来说: 索引就是数据结构
  • 当数据量很大时,为了能够方便管理数据,提高数据查询的效率,一般都会选择将数据保存到数 据库,因此数据库不仅仅是帮助用户管理数据,而且数据库系统还维护着满足特定查找算法的数 据结构,这些数据结构以某种方式引用数据,这样就可以在这些数据结构上实现高级查找算法, 该数据结构就是索引

2.MYSQL索引简介

  • MySQL中索引属于存储引擎级别的概念,不同存储引擎对索引的实现方式是不同的
  • 注意:索引是基于表的,而不是基于数据库的

1.MyISAM

  • MyISAM引擎是MySQL5.5.8版本之前默认的存储引擎,不支持事物,支持全文检索,使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址,其结构如下:
    请添加图片描述

  • 上图是以Col1为主键,MyISAM的示意图
    - 可以看出MyISAM的索引文件仅保存数据记录的地址
    - 在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复
    - 如果想在Col2上建立一个辅助索引,则此索引的结构如下图所示
    请添加图片描述

  • 同样也是一棵B+Tree,data域保存数据记录的地址

    • 因此,MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引
    • 如果指定的Key存在,则取出其data域的值,然后以data域的值为地址,读取相应数据记录
    • MyISAM的索引方式也叫做**“非聚集索引”**

2.InnoDB

  • InnoDB存储引擎支持事务,其设计目标主要面向在线事务处理的应用

  • 从MySQL数据库5.5.8版本开始,InnoDB存储引擎是默认的存储引擎

  • InnoDB支持B+树索引、全文索引、哈希索引

    • 但InnoDB使用B+Tree作为索引结构时,具体实现方式却与MyISAM截然不同
  • 第一个区别是InnoDB的数据文件本身就是索引文件

    • MyISAM索引文件和数据文件是分离的, 索引文件仅保存数据记录的地址
    • InnoDB索引,表数据文件本身就是按B+Tree组织的一个索引结构
      • 这棵树的叶节点data域保存了完整的数据记录
      • 这个索引的key是数据表的主键
      • 因此InnoDB表数据文件本身就是主索引
    • 下图是InnoDB主索引(同时也是数据文件)的示意图
      • 可以看到叶节点包含了完整的数据记录,这种索引叫做聚集索引
      • 因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键
        • MyISAM可以没有
      • 如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键
        • 如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键
          • 这个字段长度为6个字节,类型为长整型
            请添加图片描述
  • 第二个区别是InnoDB的辅助索引data域存储相应记录主键的值而不是地址,所有辅助索引都引用主键作为data域
    请添加图片描述

  • 聚集索引这种实现方式使得按主键的搜索十分高效

    • 但是辅助索引搜索需要检索两遍索引
      • 首先检索辅助索引获得主键
      • 然后用主键到主索引中检索获得记录

3.B+树做主键索引相比B树的优势

  • B+树所有值都在叶子,遍历很方便,方便区间查找
  • 队友没有建立索引的字段,全表扫描的遍历很方便
  • 分支结点存储key,一个分支结点占用更小,可以尽可能加载到缓存
  • B树不用到叶子就能找到值,B+树一定要到叶子,这是B树的一个优势
    • 但是B+树高度足够低,所以差别不大

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

10.2 JavaEE——Spring MVC入门程序

要求在浏览器发起请求&#xff0c;由Spring MVC接收请求并响应&#xff0c;具体实现步骤如下。 一、创建项目 在IDEA中&#xff0c;创建一个名称为chapter10的Maven Web项目。 &#xff08;一&#xff09;手动设置webapp文件夹 1、单击IDEA工具栏中的File→“Project Structu…

如何用GO语言实现冒泡排序算法?

本章教程,介绍一下如何用GO语言实现基础排序算法中的冒泡排序。 一、程序代码 package mainimport ("fmt""math/rand""time" )// bubbleSort 函数实现冒泡排序算法 func bubbleSort(arr []int) {n

电脑文件夹怎么加密?文件夹加密的5种方法

在数字化时代&#xff0c;信息安全显得尤为重要。对于个人电脑用户来说&#xff0c;文件夹加密是一种有效保护隐私和数据安全的方法。本文将介绍五种文件夹加密的方法&#xff0c;帮助您更好地保护自己的重要文件。 如何设置文件夹密码方法一&#xff1a;利用Windows系统自带的…

不懂就问,开通小程序地理位置接口有那么难吗?

小程序地理位置接口有什么功能&#xff1f; 若提审后被驳回&#xff0c;理由是“当前提审小程序代码包中地理位置相关接口( chooseAddress、getLocation )暂未开通&#xff0c;建议完成接口开通后或移除接口相关内容后再进行后续版本提审”&#xff0c;那么遇到这种情况&#x…

使用Rsbuild构建基于Vue3+Vant4开发h5应用

目录 一、介绍 1.1 Vant介绍 1.2 Rsbuild介绍 1.3 Vue介绍 二、构建应用 1.第一步 2.第二步 3.第三步 4.第四步 5.第五步 6.在项目中使用 Vant4 组件 7.移动端适配Rem 8. 执行 cnpm run dev 启动项目 一、介绍 1.1 Vant介绍 Vant 是一个轻量、可定制的移动端组…

Python连接Redis(简单连接、连接池连接、存取数据示例)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

C语言王国——深入自定义类型(联合体、枚举)

目录 一、引言 二、联合体 2.1 联合体类型的声明 2.2 联合体大小的计算 2.3 联合体的实践运用 2.4 用联合体测试大小端字节序 三、枚举 3.1 枚举类型的声明 3.2 枚举类型的特点 四、总结 一、引言 我们刚学完了结构体&#xff0c;相信大家对自定义类型也有了些许了解&…

【Mac】FxFactory 8 Pro for Mac(视觉特效处理包)及同类型软件介绍

软件介绍 FxFactory Pro 是一款功能强大的插件管理和创作工具&#xff0c;专为视频编辑器和特效艺术家设计&#xff0c;适用于 macOS 系统。它集成了大量的视频特效插件&#xff0c;并与多种主流视频编辑软件无缝兼容&#xff0c;如 Final Cut Pro、Premiere Pro、After Effec…

第二十七篇——通信趋势:5G和IOT的商机在哪里?

目录 一、背景介绍二、思路&方案三、过程1.思维导图2.文章中经典的句子理解3.学习之后对于投资市场的理解4.通过这篇文章结合我知道的东西我能想到什么&#xff1f; 四、总结五、升华 一、背景介绍 借势的重要性&#xff0c;但是要做到借势&#xff0c;得先看到&#xff0…

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《含氢综合能源系统多目标最优折中分布鲁棒低碳调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

vue3爷孙组件通信——provide和inject

父组件中提供数据&#xff0c;并在子组件中注入这些数据&#xff0c;从而实现了组件之间的数据传递。可用于兄弟组件通信&#xff0c;爷孙组件通信&#xff0c;父子通信。 provide( ‘注入名’, 注入值" ) 和 inject(‘注入名’) 第一代组件&#xff1a; <template>…

节能减排如何替电子行业巨头降低成本

尖端科技与环境之间的矛盾&#xff0c;已经不再是科幻小说家笔下的虚构。 先进芯片制造从熔化硅开始&#xff0c;到使用大功率激光进行光刻&#xff0c;再到创造和维护真空状态&#xff0c;以及持续清洁工作&#xff0c;每一个环节都需要大量的电力支持。据统计&#xff0c;半…

高精度乘法的实现

这是C算法基础-基础算法专栏的第九篇文章&#xff0c;专栏详情请见此处。 引入 上次我们学习了高精度加法的实现&#xff0c;这次我们要学习高精度减法的实现。 高精度乘法与高精度加法的定义、前置过程都是大致相同的&#xff0c;如果想了解具体内容&#xff0c;可以移步至我的…

IKVM.net调用Jar包实现SM4解密

近期&#xff0c;我深入学习了如何使用IKVM.net来调用Jar包&#xff0c;这次的学习经历让我对Java与.NET之间的互操作性有了更深刻的理解。IKVM.net作为一款强大的工具&#xff0c;为我们打通了Java与.NET之间的桥梁&#xff0c;使得在.NET环境中调用Java库变得简单而高效。 在…

VB.net实战(VSTO):VSTOwpf体验框架打包教程

如果是考虑到Wps用户较多&#xff0c;就不建议采用侧边栏的形式 只是个体验框架&#xff0c;界面未作美化&#xff0c;office的用户可以用任意一种窗体&#xff0c;喜欢那个界面就写那个界面&#xff0c;wps的侧边栏只能弹出一部分&#xff0c;每次需要的手动拖动。 打包了案例…

Spring Bean 生命周期详解

Spring Bean 生命周期详解 在 Spring 框架中&#xff0c;Bean 的生命周期由 Spring 容器全权管理。了解和掌握 Bean 的生命周期对于使用 Spring 开发稳定且高效的应用程序至关重要。本文将详细介绍 Spring Bean 生命周期的五个主要阶段&#xff1a;实例化、属性注入、初始化、…

如何基于Redis实现分布式锁?

分布式锁介绍 对于单机多线程来说&#xff0c;在 Java 中&#xff0c;我们通常使用 ReetrantLock 类、synchronized 关键字这类 JDK 自带的 本地锁 来控制一个 JVM 进程内的多个线程对本地共享资源的访问。 下面是我对本地锁画的一张示意图。 本地锁 从图中可以看出&#xf…

NetSuite Non-Inventory Item 公司内外采购总账影响

上篇文章提到&#xff0c;Non-Inventory Item的科目维护会根据各个企业的实际情况而有所不同&#xff0c;通常情况下都涉及外部交易&#xff0c;即对外采购与销售&#xff1b;另外也涉及到公司内部的相关交易&#xff0c;本篇以采购为例&#xff0c;来看看公司内外采购交易所对…

【分布式系列】分布式锁timeout了怎么办?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Apache IoTDB vs InfluxDB 开源版,架构性能全面对比!

分布式、端边云同步、读写查询性能&#xff0c;Apache IoTDB 与 InfluxDB 开源版的详尽对照&#xff01; 在物联网&#xff08;IoT&#xff09;领域&#xff0c;数据的采集、存储和分析是确保系统高效运行和决策准确的重要环节。随着物联网设备数量的增加和数据量的爆炸式增长&…