B树-插入
B树系列文章
1. B树-介绍
2. B树-查找
3. B树-插入
4. B树-删除
插入
根据B树的以下两个特性
- 每一个结点最多有m个子结点
- 有k个子结点的非叶子结点拥有 k − 1 个键
可以得出,B树每个结点存放键的数量是有上限的是m-1,因此插入操作可能导致结点“溢出”。
插入操作的重点和难点在于结点“溢出”后的再平衡操作
假设有一棵3阶B树,如下图所示。

通过给这棵3阶B树插入键值53,来分析B树插入键值的过程。
首先,参考查找的步骤,最终定位到53应该插入到Node21结点的最后
但是这是一个3阶B树,每个结点拥有键的最大数量为2,因此插入53会导致Node21"溢出"

由于Node21结点“溢出”,需要对Node21进行拆分,拆分方法如下
- 从该结点的原有键和新的键中选择出中位数,这个中位数作为分隔值
- 小于这一中位数的键放入左边结点,大于这一中位数的键放入右边结点,中位数作为分隔值。
- 分隔值被插入到父结点中,这可能会造成父结点分裂,分裂父结点时可能又会使它的父结点分裂,以此类推。如果没有父结点(这一结点是根结点),就创建一个新的根结点(增加了树的高度)。
下图是对Node21进行拆分的结果,Node21分裂成两个新的结点和分隔值53,53需要插入父结点中

将分隔值插入父结点中,形成Node2结点,原来的Node21结点被拆分为新的Node21和Node22结点
可以看出来,此时Node2结点键的数量达到了阶数,即达到“溢出”条件了
因此,需要继续对Node2进行拆分

对Node2进行拆分,中间值55提升到root结点,原来的Node2结点拆分成Node2和Node3两个新结点
同样的,此时root结点也“溢出”了,需要对root结点进行拆分
对root结点的拆分,最终造成B树高度的增加

对根结点进行拆分,55被提升,需要创建新的根结点存放键55,键值40和70分别构成新的两个儿子结点,分别是Node1和Node12
此时B数重新平衡了。

这里总结下
所有的插入都从根结点开始。要插入一个新的键,首先搜索这棵树找到新键应该被添加到的对应结点。将新键插入到这一结点中的步骤如下:
如果结点拥有的键数量小于最大值,那么有空间容纳新的键。将新键插入到这一结点,且保持结点中键有序。
否则的话这一结点已经满了,将它平均地拆分成两个结点:
从该结点的原有键和新的键中选择出中位数
小于这一中位数的键放入左边结点,大于这一中位数的键放入右边结点,中位数作为分隔值。
分隔值被插入到父结点中,这可能会造成父结点分裂,分裂父结点时可能又会使它的父结点分裂,以此类推。如果没有父结点(这一结点是根结点),就创建一个新的根结点(增加了树的高度)。
这里是插入的代码
/**
* 插入key
* 时间复杂度为O(logn)
**/
func (bTreeNode *BTreeNode) intert(key int, m int) ([]*BTreeNode, int) {
// 找到第一个不比key小的,注意leaf的数量比key数量多1
idx := 0
for idx < bTreeNode.keyNum && key > bTreeNode.keyList[idx] {
idx++
} // BTreeNode已有该key
if idx < bTreeNode.keyNum && bTreeNode.keyList[idx] == key {
return nil, 0
} if bTreeNode.isLeaf { // 叶子结点
// idx及idx后面元素往后移动
if idx < m-1 {
copy(bTreeNode.keyList[idx+1:], bTreeNode.keyList[idx:])
}
bTreeNode.keyList[idx] = key
bTreeNode.keyNum += 1
} else { // 非叶子结点
// 判断结点是否要满了
// 先往上提
children, midKey := bTreeNode.leafList[idx].intert(key, m)
if children != nil {
// idx是midKey的插入位置,idx后面元素往后移动
if idx < m-1 {
copy(bTreeNode.keyList[idx+1:], bTreeNode.keyList[idx:])
copy(bTreeNode.leafList[idx+2:], bTreeNode.leafList[idx+1:])
}
bTreeNode.keyList[idx] = midKey
bTreeNode.leafList[idx] = children[0]
bTreeNode.leafList[idx+1] = children[1]
bTreeNode.keyNum += 1
}
} if bTreeNode.keyNum == m { // 节点key数量超了
// 从中间将该结点一分为2
mid := bTreeNode.keyNum / 2
midKey := bTreeNode.keyList[mid] // 左儿子
leftNode := createNode(m, mid, bTreeNode.isLeaf)
copy(leftNode.keyList, bTreeNode.keyList[:mid])
copy(leftNode.leafList, bTreeNode.leafList[:mid+1]) // 右边儿子
rightNode := createNode(m, m-mid-1, bTreeNode.isLeaf)
copy(rightNode.keyList, bTreeNode.keyList[mid+1:])
copy(rightNode.leafList, bTreeNode.leafList[mid+1:]) return []*BTreeNode{leftNode, rightNode}, midKey
}
return nil, 0
} /** 插入key
* 时间复杂度O(logn)
**/
func (bTree *BTree) Insert(key int) {
children, midKey := bTree.root.intert(key, bTree.m)
if children != nil {
root := createNode(bTree.m, 1, false)
root.keyList[0] = midKey
root.leafList[0] = children[0]
root.leafList[1] = children[1]
bTree.root = root
}
}
B树-插入的更多相关文章
- AVL树插入和删除
一.AVL树简介 AVL树是一种平衡的二叉查找树. 平衡二叉树(AVL 树)是一棵空树,或者是具有下列性质的二叉排序树: 1它的左子树和右子树都是平衡二叉树, 2且左子树和右子树高度之差的 ...
- B树——插入和删除
B树--插入和删除 B树的插入 5阶B数--结点关键字个数向上取整m/2-1≤n≤m-1 即2≤n≤4 连续插入5个元素后,超出来了. 在插入key后,若导致原结点关键字数超过上限,则从中间位置(m/ ...
- AVL树插入操作实现
为了提高二插排序树的性能,规定树中的每个节点的左子树和右子树高度差的绝对值不能大于1.为了满足上面的要求需要在插入完成后对树进行调整.下面介绍各个调整方式. 右单旋转 如下图所示,节点A的平衡因子(左 ...
- AVL树插入(Python实现)
建立AVL树 class AVLNode(object): def __init__(self,data): self.data = data self.lchild = None self.rchi ...
- HDU 5687 字典树插入查找删除
题目:http://acm.hdu.edu.cn/showproblem.php?pid=5687 2016百度之星资格赛C题,直接套用字典树,顺便巩固了一下自己对字典树的理解 #include< ...
- Trie树的创建、插入、查询的实现
原文:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=28977986&id=3807947 1.什么是Trie树 Tr ...
- B+树的插入、删除(附源代码)
B+ Tree Index B+树的插入 B+树的删除 完整测试代码 Basic B+树和B树类似(有关B树:http://www.cnblogs.com/YuNanlong/p/6354029.ht ...
- AVL树的插入与删除
AVL 树要在插入和删除结点后保持平衡,旋转操作必不可少.关键是理解什么时候应该左旋.右旋和双旋.在Youtube上看到一位老师的视频对这个概念讲解得非常清楚,再结合算法书和网络的博文,记录如下. 1 ...
- [BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树
刚了一天的题终于切掉了,数据结构题的代码真**难调,这是我做过的第一道树套树题,做完后感觉对树套树都有阴影了......下面写一下做题记录. Portal Gun:[BZOJ3065]带插入区间k小值 ...
随机推荐
- linux系统健康检查脚本
#!/bin/bash echo "You are logged in as `whoami`"; if [ `whoami` != root ]; then echo " ...
- 单片机 MCU 固件打包脚本软件
1 前言 开发完 MCU 软件后,通常都会生成 hex 文件或者 bin 文件,用来做固件烧录或者升级,如果用来做产品开发,就涉及到固件版本的问题,初学者通常采用固件文件重命名来区分版本. 如果需 ...
- 论文阅读 GloDyNE Global Topology Preserving Dynamic Network Embedding
11 GloDyNE Global Topology Preserving Dynamic Network Embedding link:http://arxiv.org/abs/2008.01935 ...
- 从零开始实现lmax-Disruptor队列(四)多线程生产者MultiProducerSequencer原理解析
MyDisruptor V4版本介绍 在v3版本的MyDisruptor实现多线程消费者后.按照计划,v4版本的MyDisruptor需要支持线程安全的多线程生产者功能. 由于该文属于系列博客的一部分 ...
- appium简单使用
App 测试通常会用到的工具 adb :Android 的控制工具,用于获取Android的各种数据和控制 Appium Desktop:内嵌了Appium Server和Inspector的综合工具 ...
- 数据质量管理工具预研——Griffin VS Deequ VS Great expectations VS Qualitis
开源数据质量管理工具预研--Griffin VS Deequ VS Great expectations VS Qualitis. 概述 数据质量监控(DQC)是最近很火的一个话题,也是数据治理中 ...
- 谷歌邮箱使用python自动化发送邮件出现smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted.的解决
今天学习使用邮箱自动化办公的过程中run时一直出现如下错误;smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password n ...
- 聊聊 C++ 中的几种智能指针 (下)
一:背景 上一篇我们聊到了C++ 的 auto_ptr ,有朋友说已经在 C++ 17 中被弃用了,感谢朋友提醒,今天我们来聊一下 C++ 11 中引入的几个智能指针. unique_ptr shar ...
- Linux系列之比较命令
前言 Linux中有两个比较命令,它们分别是comm和diff,在比较文本文件的版本时通常很有用.本文介绍它们的区别和简单用法. comm命令 该命令对两个文本文件进行比较,并显示每个文件独有的行和它 ...
- 开源深度思考 - In Community We Trust
作者 | 黄东旭,PingCAP 联合创始人&CTO 出品 | CSDN(ID:CSDNnews) 业界一直流传着黄东旭的传说:小学三年级开始写代码,四五年级学习C语言,初中毕业时,已经能够用 ...