一文看懂---B树及其简单实现

news2024/11/16 3:32:51

目录

1.B树的引入

2.B树的概念

3.B树是如何插入的?

4.具体的代码实现


1.B树的引入

在以往我们在内存中搜索数据时,可以使用红黑树,平衡树,哈希表等数据结构,但是当数据量比较大,不能一次放进内存,只能放在磁盘上,需要搜索数据的时候如何处理?

这时如果用其他的数据结构:

    - 平衡二叉搜索树需要logN次,在磁盘访问中仍然是很慢的

    - 使用哈希表,在极端场景下,哈希冲突很多,效率下降很多

如何加速数据的查找速度?

        我们可以考虑将存放关键字及其映射的数据的地址放到一个内存中的搜索树的节点中,那么要访问数据时,先取这个地址去磁盘访问数据。首先,对于磁盘而言:磁盘定位到一页的信息并将其从中读出,要比读出信息进行检查的时间要长的多。如果能提高定位速度,便能有效提高访问速度,于是降低平衡树的高度便是可选之举,1970年,R.Bayer和E.mccreight提出了一种适合外查找的树,它是一种平衡的多叉树,称为B树。

2.B树的概念

首先认识B树:B树是一棵平衡的M路平衡搜索树,可以是空树或者满足下面的性质:

        1. 根节点至少有两个孩子

        2.每个分支节点有k个孩子和k-1个关键字(ceil(m/2)≤k≤m)ceil向上取整

        3.叶子节点有k-1个关键字

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

        5.每个节点当中的关键字从小到大排列,节点当中的k-1个元素正好是k个孩子包含元素的值域划分

        6.每个节点的结构为(n,A0,K1,A1,K2,A2,...,Kn,An)

         这里A0,A1...是子树根节点指针,K1,K2...是递增的关键字

        K1<K2<K3...<Kn(n为关键字个数)

        A0节点中的值<K1<A1节点中的值<K2...

        n记录实际存储的关键字个数

这里5,6规则较为抽象,直接看代码+画图理解:

template<class K, size_t M>//K 是键值,M是树的分支数
struct BTreeNode
{
	K _key[M-1];//存储关键字的数组
	BTreeNode<K, M>* _subs[M];//存储子树根节点指针的指针数组
	size_t _n;//已经存储的关键字的个数
};

当M=4时,每个节点就像这样:

 把A0当作K1的左子树,A1当作K1的右子树,A1也是K2的左子树,A2是K2的右子树......

可见是满足搜索树的规则的,于是可以实现中序遍历与搜索树类似:

先访问左子树,再访问K1,再访问右子树也就是K2的左子树,再访问K2......依次访问,不要忘了还剩下一颗右子树。

    void InOrder()
	{
		_InOrder(_root);
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		int i = 0;
		for (; i < root->_n; i++)
		{
			_InOrder(root->_subs[i]);
			cout << root->_key[i] << " ";
		}
		_InOrder(root->_subs[i]);
	}

3.B树是如何插入的?

 插入分析:

        依次插入元素(类似搜索树的查找规则,新增的数据都要插入在叶子节点上),节点满了就进行分裂,将节点右边一半的数据拷贝给兄弟节点,将本来节点的中位数往上向父亲的节点插入,并往父亲节点插入一个兄弟节点指针,如果是根节点分裂,就再创建一个根节点。

        从B树的插入可以看出B树是天然平衡的,因为插入操作本质是整棵树向右生长和向上生长都是没有产生高度差的。

以一些数据的插入为例:

 


4.具体的代码实现

#pragma once
#include<utility>
using namespace std;

template<class K,size_t M>//K 是键值,M是树的分支数
struct BTreeNode
{
	//为了方便插入满了之后分裂,两个数组都多开一个空间
	K _key[M];//存储关键字的数组
	BTreeNode<K, M>* _subs[M + 1];//存储子树根节点指针的指针数组
	BTreeNode<K, M>* _parent;//存储父节点指针,方便分裂时更新
	size_t _n;//已经存储的关键字的个数
	BTreeNode()
	{
		for (size_t i = 0; i < M; i++)
		{
			_key[i] = K();
			_subs[i] = nullptr;
		}
		_subs[M] = nullptr;
		_parent = nullptr;
		_n = 0;
	}
};

template<class K,size_t M>
class BTree
{
	typedef BTreeNode<K, M> Node;
private:
	Node* _root = nullptr;

public:
	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->_key[i])
					break;
				else if (key > cur->_n)
					++i;
				else
					return make_pair(cur, i);
			}
			parent = cur;
			cur = cur->_subs[i];//相当于往左子树跳转
		}
		return make_pair(parent, -1);//-1 表示 没有找到key
	}

	void InsertKey(Node* node, const K& key, Node* child)
	{
		int end = node->_n-1;
		while (end >= 0)
		{
			if (node->_key[end] > key)
			{
				node->_key[end + 1] = node->_key[end];
				node->_subs[end + 2] = node->_subs[end + 1];
				--end;
			}
			else break;
		}
		node->_key[end + 1] = key;
		node->_subs[end + 2] = child;
		if (child)child->_parent = node;
		node->_n++;
	}

	bool Insert(const K& 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* parent = ret.first;//find返回了需要插入的节点所在位置
		//插入满了,分裂,往上更新时,需要往父亲节点插入key 和 brother指针
		K newkey = key;
		Node* child = nullptr;
		while (1)
		{
			InsertKey(parent, newkey, child);
			if (parent->_n < M)
				return true;
			else
			{
				size_t mid = M / 2;
				//拷贝[mid+1,M-1]给兄弟节点
				Node* brother = new Node;
				int i = mid + 1;
				int j = 0;
				for (; i < M; i++)
				{
					brother->_key[j] = parent->_key[i];
					if (parent->_subs[i])
					{
						brother->_subs[j] = parent->_subs[i];
						parent->_subs[i]->_parent = brother;
					}
					++j;
				}
				if (parent->_subs[i])
				{
					brother->_subs[j] = parent->_subs[i];//最右边的指针
					parent->_subs[i]->_parent = brother;
				}

				//更新_n;
				brother->_n = j;
				parent->_n -= brother->_n + 1;

				K midkey = parent->_key[mid];
				if (parent->_parent == nullptr)//说明刚才分裂的节点是根节点
				{
					_root = new Node;
					_root->_n++;
					_root->_key[0] = midkey;
					_root->_subs[0] = parent;
					_root->_subs[1] = brother;
					parent->_parent = brother->_parent = _root;
					break;
				}
				else//parent继续往上跳,更新父亲节点
				{
					newkey = midkey;
					parent = parent->_parent;
					child = brother;
				}
			}
		}
		return true;
	}

	void InOrder()
	{
		_InOrder(_root);
	}

private:
	void _InOrder(Node* root)
	{
		if (root == nullptr)
			return;
		int i = 0;
		for (; i < root->_n; i++)
		{
			_InOrder(root->_subs[i]);
			cout << root->_key[i] << " ";
		}
		_InOrder(root->_subs[i]);
	}
};

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

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

相关文章

[附源码]计算机毕业设计Python仓储综合管理系统(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

C++PrimerPlus 第七章 函数-C++的编程模块-7.9 递归

目录 7.9 递归 7.9.1 包含一个递归调用的递归 7.9.2 包含多个递归调用的递归 7.9 递归 下面介绍一些完全不同的内容。C函数有一种有趣的特点——可以调用自己&#xff08;然而&#xff0c;与C语言不同的是&#xff0c;C不允许main()调用自己&#xff09;&#xff0c;这种功能…

SpringCloud Gateway简单使用

前言 SpringCloud Gateway是一个网关框架&#xff0c;也是现在流行的的一个网关框架&#xff0c;它包括了过滤器、限流、权限、基本路由、整合Eureka 断言predicates 等功能&#xff0c;也会介绍和zuul这个框架的一个对比&#xff0c; Spring Cloud 生态系统中的网关&#xff…

243. 一个简单的整数问题2——差分+树状数组

给定一个长度为 N 的数列 A&#xff0c;以及 M 条指令&#xff0c;每条指令可能是以下两种之一&#xff1a; C l r d&#xff0c;表示把 A[l],A[l1],…,A[r] 都加上 d。 Q l r&#xff0c;表示询问数列中第 l∼r 个数的和。 对于每个询问&#xff0c;输出一个整数表示答案。 …

《爱与自由》豆瓣9.3优秀父母的必读书

《爰和自由》 关于作者 孙瑞雪&#xff0c;中国著名的幼儿教育家与心理学专家&#xff0c;"爱和自由、规则和平等”教育精神的 发起者和倡导者&#xff0c;中国系统引进实施国际蒙特梭利教育第一人&#xff0c;成功实践了科学教育法的本土化。她发展和延伸了蒙特梭利敏感…

Oh My Posh美化CMD、Anaconda Prompt解决方案

网上搜到的Oh My Posh安装配置都是针对power shell的&#xff08;我参考这篇成功配置了针对power shell的字体和主题&#xff09;。期间遇到了无法加载文件WindowsPowerShell\profile.ps1的问题&#xff0c;参考这篇解决。由于平时我用Anaconda比较多&#xff0c;而anaconda是基…

基于ARMR和白噪声特性模型及风速威布尔分布研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

PRISEMI芯导产品推荐 | 支持路径管理功能的3A单节锂离子电池充电IC——PSC2965

PRISEMI芯导产品推荐 | 支持路径管理功能的3A单节锂离子电池充电IC——PSC2965 随着便携式电子设备功能越来越多样化和整机性能的不断提升&#xff0c;整机功耗也在面临越来越大的挑战。最直接有效的方式就是提高电池的容量来提高整机的使用时长。为了不降低用户体验&#xff0…

C# 绘图基础

一 GDI技术简介 ① GDI&#xff1a;Graphics Device Interface. ② GDI&#xff1a;GDI的改进&#xff1b; ③ 是.NET框架结构的重要组成部分&#xff1b; ④ 和GDI一样它提供对二维图形图像的支持&#xff1b; 二 .NET 对GDI的封装 三 坐标系统 GDI的坐标系统&#xff1b; …

计算机毕业设计——简单的网页设计

HTML实例网页代码, 本实例适合于初学HTML的同学。该实例里面有设置了css的样式设置&#xff0c;有div的样式格局&#xff0c;这个实例比较全面&#xff0c;有助于同学的学习,本文将介绍如何通过从头开始设计个人网站并将其转换为代码的过程来实践设计。 文章目录一、网页介绍一…

基于粒子群优化算法的分布式电源优化调度实现配电网稳定运行(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

WAYON维安提供新产品:新起点,新征程,DCDC炼成之路

WAYON维安提供新产品&#xff1a;新起点&#xff0c;新征程&#xff0c;DCDC炼成之路 新起点&#xff0c;新征程&#xff0c;DCDC炼成之路 随着新能源汽车、5G通信、工业4.0以及人工智能的快速发展&#xff0c;电源管理芯片的应用场景越来越丰富。同时传统行业&#xff0c;如网…

Day834.Dubbo如何用管程实现异步转同步 -Java 并发编程实战

Dubbo如何用管程实现异步转同步 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于Dubbo如何用管程实现异步转同步的内容。 在很多并发场景下&#xff0c;支持多个条件变量能够让并发程序可读性更好&#xff0c;实现起来也更容易。例如&#xff0c;实现一个阻塞队列&a…

星火计划学习笔记——第八讲Apollo控制模块解析与实践2

文章目录1. Apollo控制框架介绍1.1 控制模块的功能和性能要求1.2 控制模块的总体框架1.3 控制模块的代码结构1.3.1 control -> common 中的主要程序1.3.2 control -> conf 中的主要程序1.3.3 control -> controller 中的主要程序1.3.4 control -> proto 中的主要程…

Android 各镜像文件img详解

Android编译后生成文件&#xff0c;在out/target/product/lime下&#xff1a; cache.img、cust.img、metadata.img、misc.img&#xff08;本地无&#xff09;、recovery.img、super.img、userdata.img、vbmeta.img、vbmeta_system.img&#xff08;仅测试适配工作&#xff0c;而…

Python处理Excel比Vba快100倍,媳妇连连夸赞今晚不用再跪搓衣板----python实战

最近经历了一次把vb脚本改造成python脚本&#xff0c;并获得性能提升数倍的过程&#xff0c;当然&#xff0c;这个过程也不是一帆风顺&#xff0c;中间也经历了一些波折&#xff0c;但是&#xff0c;也收获了一波新的认知。正好最近有时间&#xff0c;姑且写下来记录一下。 什…

水一篇,VB+python实现智能聊天机器人案例

1.分工 理论上单python也能写&#xff0c;但是做gui开发&#xff0c;python要用到thinter库/qt库&#xff0c;稍微麻烦一点。这个案例是python做json截取&#xff0c;VB做gui开发截取json字符。 2.准备工作 编写生成file_controlv2.dll并注册&#xff0c;编写speaker.vbs,准备…

java实现获取当前日期、农历、周

大家好&#xff0c;我是雄雄。 前言 大家先看下面的一段话&#xff1a; 今天是&#xff1a;2022年12月18日&#xff0c;星期日&#xff0c;农历十一月廿五&#xff0c;早安&#x1f31e;&#x1f31e;&#x1f31e; 1.讣告 | 我国著名眼科专家兰绪达在南昌逝世&#xff0c;享…

Linux 多线程(附带线程池代码加注释)

目录 01. Linux线程概念 01.1 什么是线程 01.1.1 轻量级进程ID与进程ID之间的区别 01.1.2 总结&#xff08;重点&#xff09; 01.2 线程的优点 01.3 线程的缺点 01.4 线程异常 01.5 线程用途 02. Linux进程VS线程 02.1 进程和线程 02.2 关于多线程和多进程编程 03…

Pytorch中的卷积与反卷积(conv2d和convTranspose2d)

卷积 卷积是特征提取的常用操作&#xff0c;卷积可以改变图片的通道和大小&#xff0c;相比全连接操作&#xff0c;卷积可以减少计算量&#xff0c;并且充分融合图像的局部特征。 import torch import torch.nn as nnx torch.randn(1,1,4,4) model nn.Conv2d(in_channels1,o…