B树的原理及代码实现、B+树和B*树介绍及应用

news2024/9/18 19:24:59

目录

 一.B树介绍

(一).B树存在意义

(二).B树的规则

二.B树实现原理及代码

(一).实现原理

(二).代码

三.B+树

(一).概念

(二).应用

①MyISAM

②InnoDB

四.B*树


 一.B树介绍

(一).B树存在意义

B树主要用于磁盘文件的检索操作。众所周知,平衡二叉树(AVL树、红黑树)搜索的时间复杂度是O(log^n)。虽然很快,但如果数据在磁盘中且有上亿量级的数据,即便只有30次左右的IO操作,速度也是非常慢的。因为磁盘IO速度极慢,主要是寻道操作影响,平均8ms左右。

因此,磁盘数据的检索不适合使用平衡二叉树,B树正式上线。

 B树可以看成是压缩版的平衡二叉树,每一个节点上都有保存有多个值,且有多个叶子节点。

一般而言,B树的检索次数在个位量级,这取决于每个节点上能保存多少个值。

上亿量级的数据,红黑树可能需要30次左右,但B树只需要3-4次即可。

(二).B树的规则

1. 根节点至少有两个孩子,规定每个节点最多存m - 1个元素

2. 每个分支有k - 1个元素和k个孩子节点,其中 ceil(m/2) ≤ k ≤ m,ceil是向上取整函数。

    即孩子节点个数 = 元素个数 + 1(必须是分支节点)

3. 每个叶子节点都包含k-1个元素,其中 ceil(m/2) ≤ k ≤ m

4. 所有的叶子节点都在同一层

5. 每个节点中的元素从小到大排列

二.B树实现原理及代码

(一).实现原理

以插入3、6、4、2、7、1、5为例,假定m为3,即每个节点最多存2个元素

首先,B树的节点图示如下:

这里多开辟一个空间是为了当元素数量满3时便于之后分裂。

 依次插入,注意插入后元素要按从小到大排列(直接插入排序):

此时,元素数量已经满3,要进行分裂操作

 分裂:节点对半分裂,将4提出作为父节点(根节点),3和6叶子节点分别作为4的左右孩子节点

 之后2与4比较并插入,插入位置均是叶子节点(所有插入的元素都是这样)

 2小于4,因此插入左叶子节点,之后与3比较,小于3,插入左边。 

7大于4因此插入右叶子节点,大于6,插入6右边 

插入1后,此时右叶子节点满3了,要进行分裂: 

分裂方式一样,将2提至父节点,1和3对半分。 

2比4小,将4后移,同时4的右子树后移,1作为2的左子树,3作为2的右子树。

 5插入后,4的右子树满3,进行分裂:

此时根节点满3,也要分裂:

 将4提出作为根节点,2和6对半分,各自的叶子节点也对半分。

以上步骤包括所有B树插入的可能情况。

(二).代码

// - - -    : _key
//- - - -   : _child
template<class T, size_t M>
struct BTreeNode {
	size_t _n;//已有值数量
	T _key[M + 1];//存放值,多一个位置,便于满时添加
	BTreeNode* _child[M + 1];//存放子节点们地址
	BTreeNode* _parent;//父节点

	BTreeNode()//初始化 + 默认构造
		:_n(0)
		,_parent(nullptr)
	{
		for (size_t i = 0; i <= M; i++) {
			_key[i] = T();
			_child[i] = nullptr;
		}
	}
};

template<class T, size_t M>
class BTree {
	typedef BTreeNode<T, M> Node;

	void _insertKey(Node* cur, const T& key, Node* child)//child:右孩子
	{
		int i = cur->_n - 1;
		for (; i >= 0; i--)//这里不需要再判断key是否已有,insert中已经判断
		{
			if (key < cur->_key[i])//数据后移
			{
				cur->_key[i + 1] = cur->_key[i];
				cur->_child[i + 2] = cur->_child[i + 1];
			}
			else//key小于当前数据
			{
				break;
			}
			
		}
		//   - - -    _key
		//   - - - -  _child
		// ^   ^
		// i  child
		cur->_key[i + 1] = key;
		cur->_child[i + 2] = child;
		if (child)
		{
			child->_parent = cur;
		}
		cur->_n++;
	}
public:
	pair<Node*, int> find(const T& key)//寻找节点
	{
		Node* cur = _root;
		Node* parent = nullptr;
		while (cur)
		{
			int i = 0;
			while (i < cur->_n)
			{
				if (key > cur->_key[i])
				{
					i++;
				}
				else if (key < cur->_key[i])
				{
					break;
				}
				else
				{
					return make_pair(cur, i);
				}
			}
			parent = cur;
			cur = cur->_child[i];
		}
		return make_pair(parent, -1);//没有找到
	}

	bool insert(const T& key)//插入值
	{
		if (_root == nullptr)//插入的是第一个节点
		{
			_root = new Node;
			_root->_key[0] = key;
			_root->_n++;
			return true;
		}

		//插入的不是第一个节点
		pair<Node*, int> ret = find(key);//找节点
		if (ret.second >= 0) return false;//节点已经存在
		//节点不存在,进行插入操作
		Node* cur = ret.first;
		Node* brother = nullptr;
		T midValue = key;//因为key是const,不能直接使用
		while (1)
		{
			_insertKey(cur, midValue, brother);//先插入
			//判断cur是否已经满了
			if (cur->_n == M)
			{
				//满了,分裂
				brother = new Node;

				T keyValue = cur->_key[M / 2];
				cur->_key[M / 2] = T();

				int i = M / 2 + 1, j = 0;
				for (; i < M; i++)//分裂
				{
					brother->_key[j] = cur->_key[i];
					brother->_child[j] = cur->_child[i];
					cur->_key[i] = T();
					cur->_child[i] = nullptr;
					if (brother->_child[j])
					{
						brother->_child[j]->_parent = brother;
					}
					j++;
				}
				brother->_n = j;
				cur->_n = M - brother->_n - 1;//-1是因为还要把向上提到父节点的减去
				brother->_child[j] = cur->_child[M];//最后一个子节点也需要添加
				cur->_child[M] = nullptr;
				if (brother->_child[j])
				{
					brother->_child[j]->_parent = brother;
				}

				//判断cur是否是根节点,是就需要手动创建节点并链接叶子,因为_insertKey只能同层插入,不能更新_root
				if (cur->_parent == nullptr)
				{
					_root = new Node;
					_root->_key[0] = keyValue;
					_root->_child[0] = cur;
					_root->_child[1] = brother;
					cur->_parent = _root;
					brother->_parent = _root;
					_root->_n = 1;
					break;
				}
				else
				{
					midValue = keyValue;
					cur = cur->_parent;
				}
			}
			else
			{
				break;
			}
		}

		return true;
	}

	void levelOrder()//层序遍历
	{
		queue<Node*> qu;
		qu.push(_root);
		while (!qu.empty())
		{
			int n = qu.size();
			while (n--)
			{
				Node* node = qu.front();
				qu.pop();
				int i = 0;
				for (; i < node->_n; i++)
				{
					cout << node->_key[i] << " ";
					if (node->_child[i])
					{
						qu.push(node->_child[i]);
					}
				}
				if (node->_child[i])
				{
					qu.push(node->_child[i]);
				}
				cout << "| ";
			}
			cout << endl;

		}
	}

private:
	Node* _root = nullptr;
};

三.B+树

(一).概念

B+树是对B树的改进,具体改进如下:
1.分支节点的子树指针与元素个数相同

2. 所有叶子节点增加一个链接指针链接在一起,便于遍历元素

3. 所有元素都在叶子节点出现,且链表中的节点都是有序的

4.分支节点相当于是叶子节点的索引,叶子节点才是存储数据的数据层,叶子层的元素才记录与数据的映射。

5.分支、叶子节点首元素来自父节点,且首元素是本节点最小元素(便于查找时确定位置)

图例如下:


分支节点可以看成是叶子节点的索引,链接指针存在的意义就是便于遍历所有数据,通过叶子节点的指针可以访问兄弟叶子节点,进而遍历数据。  

插入数据时,如果叶子节点元素满了就创建一个新节点,并将数据的后一半给新节点,在父节点中记录新节点第一个数据并建立指针联系。

(二).应用

B+树典型应用就是MySQL的索引。

①MyISAM

MySQL中的MyISAM引擎就是使用B+树,主键和辅键索引结构相同,只不过主键不能重复,索引数据时,通过B+树的搜索方式(与B树基本相同,不同是可以通过链接指针直接访问下一个叶子节点)找到key值,通过key值找到数据地址,再通过地址访问数据,即非聚集索引

图例如下:

图示来源(有修改):CodingLabs - MySQL索引背后的数据结构及算法原理

 

②InnoDB

InnoDB是MySQL的默认存储引擎,与MyISAM不同的是:

1.数据文件本身就是按B+树组织的结构,即主键索引。而MyISAM的主键索引与数据文件分离,记录的是数据文件地址。

2.辅键索引映射主键的值。而MyISAM的辅键索引映射的是数据文件地址

使用主键索引时非常高效,可以直接得到完整的数据,但是使用辅键索引时需要先索引出主键值,再根据主键值索引出数据,即聚集索引

 图例如下:

图示来源(有修改):CodingLabs - MySQL索引背后的数据结构及算法原理

  

四.B*树

B*树是对B+树的进一步改良,它优化了B+树的空间利用率

结构上是在B+树基础上,分支节点中再增加指向兄弟分支节点的指针。

图例如下:

 插入数据时,如果叶子节点满了,就将一部分数据给兄弟节点。如果兄弟节点满了就进行分裂,在自己与兄弟节点中间创建新节点,将自己与兄弟节点各1/3数据给新节点并与父节点建立联系。

 退一步海阔天空,这是一种应有的心境——未名


如有错误,敬请斧正 

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

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

相关文章

Python批量采集某网站高清壁纸,这下不用担心没壁纸换了

前言 咳咳&#xff0c;担心壁纸不够用&#xff1f;想要一天换一张&#xff1f;ok &#xff0c;今天就来搞搞壁纸网站 之前老有很多高质量的网站都不见了&#xff0c;趁着这个还在&#xff0c;赶紧多保存点 话不多说 马上开始 我的表演 代码 导入模块 所有 源码 点击 此处 领…

矩阵快速幂(新手做法)

1.通过一个代码来了解矩阵乘法2.基本快速幂3.那么最后就是矩阵快速幂了4.练习模板&#xff1a;5.进阶运用&#xff0c;蓝桥杯15届省赛c语言组第9题矩阵快速幂的学习流程&#xff1a; 矩阵乘法运算规则&#xff08;线性代数基础&#xff09;快速幂的模板 1.通过一个代码来了解矩…

S32K144—什么是SBC系统基础芯片?

SBC&#xff08;System Basis Chip&#xff09;芯片在汽车电子领域可谓占一席之地了。那么什么是SBC&#xff1f;怎么用&#xff1f;用在哪里&#xff1f;主要特性&#xff1f; 可以简单理解成&#xff1a;SBC是一类拥有特出功能&#xff08;电源、通信、监控诊断、安全&#…

【附源码】基于fpga的自动售货机(使用三段式状态机实现)

目录 1、VL38 自动贩售机1 题目介绍 思路分析 代码实现 仿真文件 2、VL39 自动贩售机2 题目介绍&#xff1a; 题目分析 代码实现 仿真文件 3、状态机基本知识 1、VL38 自动贩售机1 题目介绍 设计一个自动贩售机&#xff0c;输入货币有三种&#xff0c;为0.5/1/2元&…

嵌入式Linux驱动开发笔记(八)

嵌入式Linux驱动开发笔记&#xff08;八&#xff09; 交叉编译工具说明&#xff1a; 正点原子提供两种交叉编译工具链。这两种交叉编译工具链解释如下图。 我们只需要知道上面的第二种通用的交叉编译器去学习【正点原子】 I.MX6U 嵌入式 Linux 驱动开发指南这本教程。第一种…

lscpu查看cpu信息

$ lscpu Architecture: x86_64 # 架构CPU op-mode(s): 32-bit, 64-bitAddress sizes: 45 bits physical, 48 bits virtualByte Order: Little Endian # 字节序 CPU(s): 2 # 逻辑cpu数On-line CPU(s) list: 0,1 Vendo…

GitHub上 7 个Spring Boot 优质开源项目

前后端分离已经在慢慢走进各公司的技术栈&#xff0c;不少公司都已经切换到这个技术栈上面了。即使贵司目前没有切换到这个技术栈上面&#xff0c;松哥也非常建议大家学习一下前后端分离开发&#xff0c;以免在公司干了两三年&#xff0c;SSH 框架用的滚瓜烂熟&#xff0c;出来…

hcip第四天实验

实验步骤 配置ip 所有设备可以与公网可以通信&#xff0c;在边界路由器上配置缺省指向ISP 配置普通GRE 启OSPF&#xff0c;对于不规则区域使用重发布 优化LSA更新量 R5&#xff1a; [r5]acl 2000 [r5-acl-basic-2000]rule permit source any [r5]int GigabitEthernet…

数据库知识

关系数据库标准语言SQL 写在开头这一章应该把更多的功夫放在实践上面&#xff0c;书中的语句繁杂多样&#xff0c;不亲自写一写是不容易学会的。其次&#xff0c;理论学了那么多&#xff0c;不会有人不想实践敲一下吧[doge]&#xff1f;建议先搞数据更新中的插入数据后再看查询…

【回答问题】ChatGPT上线了!写出coco数据集的图像分类训练和预测函数代码?

写出coco数据集的图像分类训练代码 好的&#xff0c;我会尽力写出图像分类训练代码。首先&#xff0c;你需要准备好 COCO 数据集的图像和标签&#xff0c;并将其转换成适合你模型使用的格式。其次&#xff0c;你需要选择一个图像分类模型&#xff0c;并定义好模型的结构。接下…

【课程太贵,学习无门,让你们了解CSS3入门知识】

文章目录‍❤️‍&#x1f525;CSS简介‍‍‍‍❤️‍&#x1f525;CSS选择器❣️选择器一❣️选择器二❣️关系选择器‍❤️‍&#x1f525;CSS属性❣️字体属性❣️背景属性❣️文本属性❣️表格属性‍❤️‍&#x1f525;CSS文档流‍❤️‍&#x1f525;CSS浮动‍❤️‍&…

【编程导航】设计模式学习笔记

设计模式日常学习(一)设计模式 软件设计模式的概念 软件设计模式&#xff08;Software Design Pattern&#xff09;&#xff0c;又称设计模式&#xff0c;是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的…

windows下编译dtkwidget

参考 https://blog.csdn.net/yuxue321/article/details/109552210 步骤 安装官方qt5.12.3&#xff0c;安装时勾选64位版本、32位版本 下载glib和pkg&#xff0c;到mingw73_32目录下&#xff0c;解压到当前文件夹 https://brltty.app/archive/Windows/MinGW/glib_2.34.3-1_wi…

【大数据】Hadoop完全分布式配置(超详细)

文章目录概述1.准备Linux2.安装JDK3.克隆两台虚拟机4.免密登陆5.安装Hadoop6.配置Hadoop配置文件7.启动服务8.在集群上测试一个jar包-单词统计的功能问题总结概述 Hadoop完全分布式配置-具体步骤如下 默认前提&#xff1a; 1.在Windows平台下安装Vmware平台&#xff08;默认已…

数据库的三大范式

数据库的三大范式 设计关系数据库时&#xff0c;需要遵从不同的规范要求&#xff0c;设计出合理的关系型数据库&#xff0c;这些不同的规范要求被称为不同的范式&#xff0c;越高的范式数据冗余度越低。 实际开发中涉及到的范式一般有三种&#xff1a;第一范式、第二范式、第…

WindowsTerminal_01 配置SSH连接

文章目录1 前言2 过程参考1 前言 windows terminal 功能强大&#xff0c;可以自定义终端。由于实验需求&#xff0c;需要用到Linux服务器&#xff0c;所以打算使用Windows Termial 来配置终端&#xff0c;以此来方便地登录服务器&#xff0c;执行一些简单的命令 2 过程 自定…

MongoDB基础

目录简介安装基操pymongo简介 MongoDB 是一个非关系型数据库非常适合超大数据集的存储&#xff0c;由 C 语言编写&#xff0c;旨在为 WEB 应用提供可扩展的高性能数据存储解决方案MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰…

TC275——03开发环境搭建

开发环境与工具链的搭配有很多&#xff0c;这里选择最省事的英飞凌自己推出的一款基于eclipse的IDE&#xff0c;主要是学习&#xff0c;不用于商业用途。 安装ADS开发环境 下载网址&#xff1a; AURIX™ Development Studio - Infineon Technologies 下载这个安装包 双击安装…

Springboot-Vue项目框架每部分的介绍

Springboot-Vue项目框架每部分的介绍 文章目录Springboot-Vue项目框架每部分的介绍前端后端前端 后端 如上图所示&#xff0c;在Springboot项目中&#xff0c;目录结构有代码层结构和资源文件的结构 SpringBoot项目框架对工程结构并没有特殊的限制&#xff0c;只要是良好的工程…

C语言--图书管理项目

C语言图书管理系统项目 第一节 C 语言基础以及基本数据类型 第二节 C 语言运算符 第三节 C 语言控制语句 第四节 C 语言自定义函数 第五节 C 语言修饰变量的关键字 第六节 C 语言构造数据类型–数组 第七节 C 语言字符串 第八节 C 语言指针 第九节 指针与函数、指针函数、函数…