赞
踩
磁盘由一个单片或者一组多个盘片组成盘组,每个盘片有两个面,然后盘面上有许多称为磁道的圆圈,数据就是存储在这些磁道上的,磁盘驱动器进行数据读写时,盘片绕着中间的主轴旋转,当磁道绕过读写头(磁头:可以简单理解为连接磁盘和内存的其中一端,就是移动臂的一端),就可以进行数据读写了,磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、块号(磁道上的盘块),读写磁盘分三个步骤:
(1)定位或者查找柱面
移动臂根据柱面号使磁头移动到所需要的柱面上,一般是盘组,所以这里就缺点了每个盘面上位置一样的磁道
(2)根据盘面号来确定指定盘面上的磁道
(3)盘片开始旋转,将指定块号的磁道段移动至磁头下
时间分析:步骤1是查找时间,代价最高,达到0.1s左右,步骤3是等待时间,这个比较快,旋转一圈大约0.0083s,传输时间:数据从总线传输到内存
磁盘读取数据是以盘块(block)为基本单位的。位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘IO代价主要花费在查找时间Ts上。因此我们应该尽量将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在读/写信息时尽量减少磁头来回移动的次数,避免过多的查找时间Ts。所以就出现了B数。
因为B树的原英文名称为B-tree,而国内很多人喜欢把B-tree译作B-树,所以B-树就是B数,并不是另外一种树。
B 树又叫平衡多路查找树, 一棵m阶的B 树特性如下:
(1)树中每个结点最多含有m个孩子(m>=2);
(2)除根结点和叶子结点外,其它每个结点至少有[ceil(m / 2)]个孩子(其中ceil(x)是一个取上限的函数);
(3)若根结点不是叶子结点,则至少有2个孩子(特殊情况:没有孩子的根结点,即根结点为叶子结点,整棵树只有一个根节点);
(4)所有叶子结点都出现在同一层,叶子结点不包含任何关键字信息(可以看做是外部接点或查询失败的接点,实际上这些结点不存在,指向这些结点的指针都为null);(读者反馈@冷岳:这里有错,叶子节点只是没有孩子和指向孩子的指针,这些节点也存在,也有元素。@研究者July:其实,关键是把什么当做叶子结点,因为如红黑树中,每一个NULL指针即当做叶子结点,只是没画出来而已)。
(5)每个非终端结点中包含有n个关键字信息: (n,P0,K1,P1,K2,P2,…,Kn,Pn)。其中:
a) Ki (i=1…n)为关键字,且关键字按顺序升序排序K(i-1)< Ki。
b) Pi为指向子树根的接点,且指针P(i-1)指向子树种所有结点的关键字均小于Ki,但都大于K(i-1)。
c) 关键字的个数n必须满足: [ceil(m / 2)-1]<= n <= m-1。
注:节点的关键字数比孩子数少一个
B树中每一个结点能包含的关键字数有一个上界和下界。这个下界可以用一个称作B树的最小度数(最小度数即内节点中节点最小孩子数目)m(m>=2)表示,用关键字,也就是度来讨论:
(1)每个非根的内结点至少有m个子女,每个非根的结点必须至少含有m-1个关键字,如果树是非空的,则根结点至少包含一个关键字;
(2)每个结点可包含至多2m-1个关键字。所以一个内结点至多可有2m个子女。如果一个结点恰好有2m-1个关键字,我们就说这个结点是满的(而稍后介绍的B树作为B树的一种常用变形,B树中要求每个内结点至少为2/3满,而不是像这里的B树所要求的至少半满);
(3)当关键字数m=2(t=2的意思是,mmin=2,m可以>=2)时的B树是最简单的,但他也不是二叉树
结合背景,我们知道B树中的每个结点根据实际情况可以包含大量的关键字信息和分支(当然是不能超过磁盘块的大小,根据磁盘驱动(disk drives)的不同,一般块的大小在1k~4k左右);假设一个盘块正好存放一个B树的节点,那么查找某个节点的数据,就需要进行磁盘IO一次盘块到内存,那么树的高度就相当于IO的次数了,这样树的高度降低了,这就意味着查找一个元素只要很少结点从外存磁盘中读入内存(这个过程就是磁盘IO),很快访问到要查找的数据,就是说利用B树可以大大降低磁盘IO的次数,因为一个节点可以存很多东西,大大降低了树的高度。
注意: Max(keyNum)=M-1
分裂:插入一个关键码,导致关键码数大于节点最大允许的关键字数M-1,那么就要将该节点进行分裂为两个节点,然后将排在中间的关键码提升到父节点中去。
插入只发生在叶节点:
(1)若该结点中关键码个数小于M-1,则直接插入。
(2)如该结点中关键码个数等于M-1,则将引起结点的分裂。以中间关键码为界将结点一分为二,产生一个新结点, 并把中间关键码插入到父结点(h-1层)中。
(3)向父亲结点插入中间关键字的时候,重复以上两个步骤最坏情况一直分裂到根结点,建立一个新的根结点,整个B树增加一层
注意: Min(keyNum)=M/2-1
合并:如果一个节点删除某个关键码后,该节点的关键码数小于最小的关键码数M/2-1,如果不在叶子节点,他的前驱或者后继就是他的继任者,继任者取代他后,删除他的继任者,而他的继任者一定在叶子节点,所以删除最终要考虑的就是在叶子怎么去删除一个节点:
如果兄弟等于M/2-1就合并,就是将父节点中的关键码下降做为中间关键码,并且和删除关键码的节点和他的兄弟节点进行合并成一个节点;否则就不需合并,说明某个兄弟的关键码数大于最小的M/2-1,只需要将父关键码下降到删除节点中,将兄弟节点提升到父节点中即可
1)删除操作的两个步骤
第一步骤:在树中查找被删关键字K所在的地点
第二步骤:进行删去K的操作
(2)删去K的操作
B-树是二叉排序树的推广,中序遍历B-树同样可得到关键字的有序序列(具体遍历算法【参见练习】)。任一关键字K的中序前趋(后继)必是K的左子树(右子树)中最右(左)下的结点中最后(最前)一个关键字。若被删关键字K所在的结点非树叶,则用K的中序前趋(或后继)S代K,然后从叶子中删去S,那么就相等于我们要考虑的就是在叶子删除一个节点(可能是自己活着他的继任者)。从叶子*x开始删去某关键字K的三种情形为:
1)情形一:若x->keynum>Min,则只需删去K及其右指针(*x是叶子,K的右指针为空)即可使删除操作结束。
2)情形二:若x->keynum=Min,该叶子中的关键字个数已是最小值,删K及其右指针后会破坏B-树的性质(3)。若x的左(或右)邻兄弟结点y中的关键字数目大于Min,则将y中的最大(或最小)关键字上移至双亲结点parent中,而将parent中相应的关键字下移至x中。显然这种移动使得双亲中关键字数目不变;y被移出一个关键字,故其keynum减1,因它原大于Min,故减少1个关键字后keynum仍大于等于Min;而x中已移入一个关键字,故删K后x中仍有Min个关键字。涉及移动关键字的三个结点均满足B-树的性质(3)。 请读者验证,上述操作后仍满足B-树的性质(1)。移动完成后,删除过程亦结束。
3)情形三:若x及其相邻的左右兄弟(也可能只有一个兄弟)中的关键字数目均为最小值Min,则上述的移动操作就不奏效,此时须x和左或右兄弟合并。不妨设x有右邻兄弟y(对左邻兄弟的讨论与此类似),在x中删去K后,将双亲结点parent中介于x和y之间的关键字K,作为中间关键字,与并x和y中的关键字一起"合并"为一个新的结点取代x和y。因为x和y原各有Min个关键字,从双亲中移人的K’抵消了从x中删除的K,故新结点中恰有2Min(即2「m/2」-2≤m-1)个关键字,没有破坏B-树的性质(3)。但由于K’从双亲中移到新结点后,相当于从parent中删去了K’,若parent->keynum原大于Min,则删除操作到此结束;否则,同样要通过移动parent的左右兄弟中的关键字或将*parent与其 左右兄弟合并的方法来维护B-树性质。最坏情况下,合并操作会向上传播至根,当根中只有一个关键字时,合并操作将会使根结点及其两个孩子合并成一个新的根,从而使整棵树的高度减少一层。如图:
/** *定义B树的节点 */ class BTNode<K extends Comparable<K>, V> { // 构成B树的最小度数 public final static int MIN_DEGREE = 3; // 除根节点外,每个结点中总键数的下限 public final static int LOWER_BOUND_KEYNUM = MIN_DEGREE - 1; // 包含根节点外,每个结点中总键数的上限 public final static int UPPER_BOUND_KEYNUM = (MIN_DEGREE * 2) - 1; protected boolean mIsLeaf;// 标记此节点是否为叶子结点 protected int mCurrentKeyNum;// 此节点的键数量计数器 protected BTKeyValue<K, V>[] mKeys;// 用于存键值对的数组 protected BTNode<K, V>[] mChildren;// 用于存子结点的数组 /** * 构造函数 */ @SuppressWarnings("unchecked") public BTNode() { mIsLeaf = true; mCurrentKeyNum = 0; mKeys = new BTKeyValue[UPPER_BOUND_KEYNUM]; mChildren = new BTNode[UPPER_BOUND_KEYNUM + 1]; } protected static BTNode<?, ?> getChildNodeAtIndex(BTNode<?, ?> btNode, int keyIdx, int nDirection) { if (btNode.mIsLeaf) { return null; } keyIdx += nDirection; if ((keyIdx < 0) || (keyIdx > btNode.mCurrentKeyNum)) { throw new IllegalArgumentException(); } return btNode.mChildren[keyIdx]; } /** * 返回btNode节点中位于keyIdx位上的键左边的子结点 * @param btNode * @param keyIdx * @return */ protected static BTNode<?, ?> getLeftChildAtIndex(BTNode<?, ?> btNode, int keyIdx) { return getChildNodeAtIndex(btNode, keyIdx, 0); } /** * 返回btNode节点中位于keyIdx位上的键右边的子结点 * @param btNode * @param keyIdx * @return */ protected static BTNode<?, ?> getRightChildAtIndex(BTNode<?, ?> btNode, int keyIdx) { return getChildNodeAtIndex(btNode, keyIdx, 1); } /** * @param parentNode * @param keyIdx * @return 返回父结点的keyIdx位上的子结点的左兄弟结点 */ protected static BTNode<?, ?> getLeftSiblingAtIndex(BTNode<?, ?> parentNode, int keyIdx) { return getChildNodeAtIndex(parentNode, keyIdx, -1); } /** * * @param parentNode * @param keyIdx * @return 返回父结点的keyIdx位上的子结点的右兄弟结点 */ protected static BTNode<?, ?> getRightSiblingAtIndex(BTNode<?, ?> parentNode, int keyIdx) { return getChildNodeAtIndex(parentNode, keyIdx, 1); } /** * 判断父结点的keyIdx位上的子结点是否存在左兄弟结点 * @param parentNode * @param keyIdx * @return */ protected static boolean hasLeftSiblingAtIndex(BTNode<?, ?> parentNode, int keyIdx) { if (keyIdx - 1 < 0) { return false; } else { return true; } } /** * 判断父结点的keyIdx位上的子结点是否存在右兄弟结点 * @param parentNode * @param keyIdx * @return */ protected static boolean hasRightSiblingAtIndex(BTNode<?, ?> parentNode, int keyIdx) { if (keyIdx + 1 > parentNode.mCurrentKeyNum) { return false; } else { return true; } } } /** * 定义存键值对 * */ class BTKeyValue<K extends Comparable<K>, V> { protected K mKey; protected V mValue; public BTKeyValue(K mKey, V mValue) { super(); this.mKey = mKey; this.mValue = mValue; } } /** *定义B树 */ public class BTree<K extends Comparable<K>, V> { //定义根节点 private BTNode mRoot; /** * 查找 * * @param key * @return 若键存在,则返回键所对应的值。若键不存在,则抛出异常。 */ public V search(K key) { checkKey(key); BTNode<K, V> currentNode = mRoot; // 迭代查找每一个可能存储key的结点 while (currentNode != null) { int possibleIdx = binarySearch(currentNode, key); BTKeyValue<K, V> possibleKeyValue = null; // 判断二分查找返回位置索引处的元素是否为查找的元素,若是则返回其值,如不是,则迭代到下一个可能的结点中查找 if (possibleIdx < currentNode.mCurrentKeyNum && key.compareTo( (possibleKeyValue = currentNode.mKeys[possibleIdx]).mKey) == 0) { return possibleKeyValue.mValue; } else { currentNode = currentNode.mChildren[possibleIdx]; } } return null; } /** * 插入 * * @param key 键不允许为null * @param value */ public void insert(K key, V value) { checkKey(key); if (mRoot == null) { mRoot = createNode(); } // 使用递归的方法将键-值对插入到BTree结构中 mRoot = insert(mRoot, key, value); } /** * 删除 * @param key * @return */ public V deleteKey(K key) { checkKey(key); V v = search(key); // 递归的删除键key mRoot = deleteKey(mRoot, key); return v; } /** * 用二分查找法查找结点中键的位置,若找到返回键的位置,若没找到,则返回键应该插入的位置 * * @param btNode * @param key * @return */ private int binarySearch(BTNode<K, V> btNode, K key) { BTKeyValue<K, V>[] keys = btNode.mKeys; int lo = 0; int hi = btNode.mCurrentKeyNum - 1; while (lo <= hi) { int mid = (hi - lo) / 2 + lo; int cmp = key.compareTo(keys[mid].mKey); if (cmp == 0) { return mid; } else if (cmp > 0) { lo = mid + 1; } else if (cmp < 0) { hi = mid - 1; } } return lo; } /** * 递归插入方法 * * @param x 要插入到的结点 * @param key * @param value * @return */ private BTNode<K, V> insert(BTNode<K, V> x, K key, V value) { // 1.首先判断此节点是否已经为满,若满,则将此节点分裂 if (x.mCurrentKeyNum == BTNode.UPPER_BOUND_KEYNUM) { x = split(x); } // 2.对没有满的结点进行键值对的查找,找出可能的键值对索引,和可能的键值对 int possibleIdx = binarySearch(x, key); /* * 由于第一步操作会确定当前节点为非满结点,故不用担心数组越界问题(不然试想,当此结点已满,且要插入的键大于此节点中所有键, * 故possibleIdx的值会等于UPPER_BOUND_KEYNUM,故造成越界) */ BTKeyValue<K, V> possibleKeyValue = x.mKeys[possibleIdx]; /* * 3.判断可能的键值对中的键是否与要插入的键相同(当要插入的键大于当前结点中所有的键时,possibleKeyValue取值为x.mKeys[x. * mCurrentKeyNum]为null,故要判断possibleKeyValue的值是否为空,以防止空指针异常) * 如果相同则直接替换当前值为插入值,并返回当前结点(用于更新) */ if (possibleKeyValue != null && key.compareTo(possibleKeyValue.mKey) == 0) { possibleKeyValue.mValue = value; return x; } /* * 4.当前节点为叶子节点时,直接插入(由于在最前边进行了当前结点是否为满的判断,并做了相应的处理,故到此步插入键值对后,此节点最多为满,且不会溢出) * 当前结点不为叶子结点时,递归到下一个可能的结点继续寻找、插入 */ if (x.mIsLeaf) { // 4.1 for (int i = x.mCurrentKeyNum; i > possibleIdx; i--) { x.mKeys[i] = x.mKeys[i - 1]; } x.mKeys[possibleIdx] = new BTKeyValue<>(key, value); x.mCurrentKeyNum++; mSize++; } else { // 4.2 BTNode<K, V> t = insert(x.mChildren[possibleIdx], key, value); /* * 4.3判断当返回的结点中的键值对数量为1时,说明返回的结点经过了分裂,故需要将其合并到当前节点中(同上理,合并后,当前结点最多为满) */ if (t.mCurrentKeyNum == 1) { // 4.3.1移动当前节点中的键值对为要合并的键值对腾出地方,并存入 for (int i = x.mCurrentKeyNum; i > possibleIdx; i--) { x.mKeys[i] = x.mKeys[i - 1]; } x.mKeys[possibleIdx] = t.mKeys[0]; // 4.3.2移动当前节点中的子结点为要合并的子结点腾出地方,并存入 for (int i = x.mCurrentKeyNum + 1; i > possibleIdx + 1; i--) { x.mChildren[i] = x.mChildren[i - 1]; } x.mChildren[possibleIdx] = t.mChildren[0]; x.mChildren[possibleIdx + 1] = t.mChildren[1]; // 4.3.3更新当前结点的键值对计数器 x.mCurrentKeyNum++; } } return x; } /** * @param x * @param key * @return */ private BTNode<K, V> deleteKey(BTNode<K, V> x, K key) { // 1.获取要删除的键可能处在当前结点上的索引位置 int possibleIdx = binarySearch(x, key); // 2.根据当前结点是否为叶子结点分情况讨论 if (x.mIsLeaf == true) { // 2.1当前结点为叶子节点 if (possibleIdx < x.mCurrentKeyNum && key.compareTo(x.mKeys[possibleIdx].mKey) == 0) { // 2.1.1判断在当前结点上possible索引位置上的键是否与要删除的键相等(前提是possible索引小于当前节点键的数量,负责会出现空指针异常) // 如果相等,则直接删除此键,否则,此键不存在树中,不做任何操作 for (int i = possibleIdx; i < x.mCurrentKeyNum - 1; i++) { x.mKeys[i] = x.mKeys[i + 1]; } x.mKeys[x.mCurrentKeyNum - 1] = null; x.mCurrentKeyNum--; mSize--; } } else { // 2.2当前结点不为子结点 if (possibleIdx < x.mCurrentKeyNum && key.compareTo(x.mKeys[possibleIdx].mKey) == 0) { // 2.2.1判断在当前结点上possible索引位置上的键是否与要删除的键相等(前提是possible索引小于当前节点键的数量,负责会出现空指针异常) // 如果成立,用possible索引处的子结点的最大键替换要删除的键 // 1)记住possilbe索引处子结点的最大键 BTKeyValue<K, V> keyNeedToSwim = maxKey(x.mChildren[possibleIdx]); // 2)将1)中记住的键删除 x = deleteKey(x, keyNeedToSwim.mKey); // 3)将key替换为记住的键 possibleIdx = binarySearch(x, key); x.mKeys[possibleIdx] = keyNeedToSwim; } else { // 2.2.2 // 如果不成立,递归的在possible索引处子结点上删除键key // 递归删除 BTNode<K, V> t = deleteKey(x.mChildren[possibleIdx], key); // 检测删除后返回结点的状态,如果不满足键数量>=最低度数-1,则酌情旋转或合并 if (t.mCurrentKeyNum < BTNode.LOWER_BOUND_KEYNUM) { // 不满足键数量>=最低度数-1 // 判断返回结点的兄弟结点的状况,若兄弟结点的键数量>最低度数-1,则旋转(向兄弟结点借键),若兄弟结点的键数量<=最低度数-1,则与兄弟结点合并 if (BTNode.hasLeftSiblingAtIndex(x, possibleIdx)) { if (BTNode.getLeftSiblingAtIndex(x, possibleIdx).mCurrentKeyNum > BTNode.LOWER_BOUND_KEYNUM) { leftRotate(x, possibleIdx); } else { leftMerge(x, possibleIdx); } } else if (BTNode.hasRightSiblingAtIndex(x, possibleIdx)) { if (BTNode.getRightSiblingAtIndex(x, possibleIdx).mCurrentKeyNum > BTNode.LOWER_BOUND_KEYNUM) { rightRotate(x, possibleIdx); } else { rightMerge(x, possibleIdx); } } // 判断删完后根节点是否没有键存在,若没有,则将根节点重新赋值 if (x == mRoot && x.mCurrentKeyNum == 0) { x = x.mChildren[0]; } } } } return x; } /** * @return */ public BTKeyValue<K, V> minKey() { return minKey(mRoot); } /** * @param x * @return */ private BTKeyValue<K, V> minKey(BTNode<K, V> x) { if (x == null) { return null; } if (x.mChildren[0] != null) { return minKey(x.mChildren[0]); } else { return x.mKeys[0]; } } /** * @return */ public BTKeyValue<K, V> maxKey() { return maxKey(mRoot); } /** * @param x * @return */ private BTKeyValue<K, V> maxKey(BTNode<K, V> x) { if (x == null) { return null; } if (x.mChildren[x.mCurrentKeyNum] != null) { return maxKey(x.mChildren[x.mCurrentKeyNum]); } else { return x.mKeys[x.mCurrentKeyNum - 1]; } } /** * 将满结点x分裂为含有一个键值对的父结点和两个子结点,并返回父结点的链接 * * @param x * @return */ private BTNode<K, V> split(BTNode<K, V> x) { int mid = x.mCurrentKeyNum / 2; BTNode<K, V> left = new BTNode<>(); for (int i = 0; i < mid; i++) { left.mKeys[i] = x.mKeys[i]; left.mChildren[i] = x.mChildren[i]; } left.mChildren[mid] = x.mChildren[mid]; left.mIsLeaf = x.mIsLeaf; left.mCurrentKeyNum = mid; BTNode<K, V> right = new BTNode<>(); for (int i = mid + 1; i < x.mCurrentKeyNum; i++) { right.mKeys[i - mid - 1] = x.mKeys[i]; right.mChildren[i - mid - 1] = x.mChildren[i]; } right.mChildren[x.mCurrentKeyNum - mid - 1] = x.mChildren[x.mCurrentKeyNum]; right.mIsLeaf = x.mIsLeaf; right.mCurrentKeyNum = x.mCurrentKeyNum - mid - 1; BTNode<K, V> top = new BTNode<>(); top.mCurrentKeyNum = 1; top.mIsLeaf = false; top.mKeys[0] = x.mKeys[mid]; top.mChildren[0] = left; top.mChildren[1] = right; return top; } /** * 从右结点借一个键值对过来 * * @param x * @param possibleIdx * @return */ private BTNode<K, V> rightRotate(BTNode<K, V> x, int possibleIdx) { // 获取右节点和右节点中最小的键值对 BTNode<K, V> rightNode = x.mChildren[possibleIdx + 1]; BTKeyValue<K, V> rightKey = rightNode.mKeys[0]; // 获取右节点中最小的结点 BTNode<K, V> rightFirstNode = rightNode.mChildren[0]; // 获取父结点交换位置的键值对 BTKeyValue<K, V> topKey = x.mKeys[possibleIdx]; // 获取需补齐键值对的节点,并将父结点交换位置的键值对加到此节点的最高位 BTNode<K, V> possibleNode = x.mChildren[possibleIdx]; possibleNode.mKeys[possibleNode.mCurrentKeyNum] = topKey; // 将右节点中最小的结点添加到此节点 possibleNode.mChildren[possibleNode.mCurrentKeyNum + 1] = rightFirstNode; possibleNode.mCurrentKeyNum++; // 将父结点拿走键值对的位置填上右节点提出的键值对 x.mKeys[possibleIdx] = rightKey; // 将右节点提出的键值对和最小结点在右节点中删除 for (int i = 1; i < rightNode.mCurrentKeyNum; i++) { rightNode.mKeys[i - 1] = rightNode.mKeys[i]; } rightNode.mKeys[rightNode.mCurrentKeyNum - 1] = null; for (int i = 1; i < rightNode.mCurrentKeyNum + 1; i++) { rightNode.mChildren[i - 1] = rightNode.mChildren[i]; } rightNode.mChildren[rightNode.mCurrentKeyNum] = null; rightNode.mCurrentKeyNum--; return x; } /** * ‘ * * @param x 父结点 * @param possibleIdx 需要补充键值对的子结点的索引 * @return */ private BTNode<K, V> leftRotate(BTNode<K, V> x, int possibleIdx) { // 获取左节点和左节点中最大的键值对 BTNode<K, V> leftNode = x.mChildren[possibleIdx - 1]; BTKeyValue<K, V> leftKey = leftNode.mKeys[leftNode.mCurrentKeyNum - 1]; // 获取左节点中最大的结点 BTNode<K, V> leftLastNode = leftNode.mChildren[leftNode.mCurrentKeyNum]; // 获取父结点交换位置的键值对 BTKeyValue<K, V> topKey = x.mKeys[possibleIdx - 1]; // 获取需补齐键值对的节点,并移动其中的键值对将最低位空出来:以用来填充从父结点交换过来的键值对 BTNode<K, V> possibleNode = x.mChildren[possibleIdx]; for (int i = possibleNode.mCurrentKeyNum; i > 0; i--) { possibleNode.mKeys[i] = possibleNode.mKeys[i - 1]; } // 同理对此节点的子结点 for (int i = possibleNode.mCurrentKeyNum + 1; i > 0; i--) { possibleNode.mChildren[i] = possibleNode.mChildren[i - 1]; } // 填充键值对和其带过来的链接,并将键数量计数器加1 possibleNode.mKeys[0] = topKey; possibleNode.mChildren[0] = leftLastNode; possibleNode.mCurrentKeyNum++; // 将父结点拿走键值对的位置填上左节点提出的键值对 x.mKeys[possibleIdx - 1] = leftKey; // 将左节点提出的键值对和子结点在左节点中删除 leftNode.mKeys[leftNode.mCurrentKeyNum - 1] = null; leftNode.mChildren[leftNode.mCurrentKeyNum] = null; leftNode.mCurrentKeyNum--; return x; } /** * 合并父结点中位于possibleIdx和possibleIdx+1处的俩结点(由此可见可用执行做合并来完成右合并同样的任务) * * @param x * @param possibleIdx * @return */ private BTNode<K, V> rightMerge(BTNode<K, V> x, int possibleIdx) { return leftMerge(x, possibleIdx + 1); } /** * 合并父结点中位于possibleIdx和possibleIdx-1处的俩结点 * * @param x * @param possibleIdx * @return */ private BTNode<K, V> leftMerge(BTNode<K, V> x, int possibleIdx) { // 获取左节点 BTNode<K, V> leftNode = x.mChildren[possibleIdx - 1]; // 获取父结点要合并到左节点的键值对 BTKeyValue<K, V> topKey = x.mKeys[possibleIdx - 1]; // 获取需要合并的结点 BTNode<K, V> possibleNode = x.mChildren[possibleIdx]; // 将父结点获取的键值对放入左节点 leftNode.mKeys[leftNode.mCurrentKeyNum] = topKey; // 将需要合并的结点的键值对全部放入左节点 for (int i = 0; i < possibleNode.mCurrentKeyNum; i++) { leftNode.mKeys[i + leftNode.mCurrentKeyNum + 1] = possibleNode.mKeys[i]; } // 同理,将结点链接也移过来 for (int i = 0; i < possibleNode.mCurrentKeyNum + 1; i++) { leftNode.mChildren[i + leftNode.mCurrentKeyNum + 1] = possibleNode.mChildren[i]; } // 更新左节点的键值对计数器 leftNode.mCurrentKeyNum += 1 + possibleNode.mCurrentKeyNum; // 更新父结点 for (int i = possibleIdx; i < x.mCurrentKeyNum; i++) { x.mKeys[i - 1] = x.mKeys[i]; } x.mKeys[x.mCurrentKeyNum - 1] = null; for (int i = possibleIdx; i < x.mCurrentKeyNum; i++) { x.mChildren[i] = x.mChildren[i + 1]; } x.mChildren[x.mCurrentKeyNum] = null; x.mCurrentKeyNum--; return x; } }
是应文件系统所需而产生的一种B-tree的变形树,更适合实际应用中操作系统的文件索引和数据库索引。
(1)有n棵子树的结点中含有n个关键字,这个和B树不同
(2)所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针,且叶子结点本身依关键字的大小自小而大的顺序链接,而B 树的叶子节点并没有包括全部需要查找的信息
(3)所有的非终端结点可以看成是索引部分,结点中仅含有其子树根结点中最大(或最小)关键字,而B 树的非终节点也包含需要查找的有效信息。
更适合实际应用中操作系统的文件索引和数据库索引
(1)B±tree的磁盘读写代价更低,提高了磁盘的IO性能
B±tree的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了
(2)B±tree的查询效率更加稳定
B树没有解决元素遍历的效率低下的问题, B+树只要遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作
分裂:和B树的一样
当一个结点满(关键码数超过M),分配一个新的结点,并将原结点中1/2的数据复制到新结点,最后在父结点中增加新结点的指针,插入都是在叶子节点
(1)当叶子节点的关键码数不满(超过M),内部节点不满
直接将关键码插入叶子节点
(2)当叶子节点的关键码数满(超过M),内部节点不满
插入后将叶子节点拆分,小于中间关键码的放新的左节点,大于等于中间关键码的放新的右节点,将中间关键码放入内部节点(索引节点)中
(3)当叶子节点的关键码满,内部节点也满
插入后将叶子节点拆分,小于中间关键码的放新的左节点,大于等于中间关键码的放新的右节点,将中间关键码放入内部节点(索引节点)中;继续递归拆分内部节点
合并:和B树的一样
如果一个节点删除某个关键码后,该节点的关键码数小于最小的关键码数M/2,
如果兄弟等于M/2就合并,直接和兄弟节点合并成一个新的节点,同时删除父节点中的关键码;否则就不需要合并,只是需要从兄弟节点借一个节点过来,然后修改父节点的关键码,注意删除一定在叶子节点,因为关键码全都存在叶子节点,内部节点只是存索引
(1)当叶子节点的关键码数不低于填充因子(M/2),内部节点也是不低于
直接删除关键码,如果该关键码还是内部节点的索引,也要在内部节点删除他
(2)当叶子节点的关键码数低于填充因子(M/2),内部节点不低于
如果叶子节点的兄弟节点关键码数大于M/2,那么就从兄弟节点借,否则就和他的兄弟节点合并;然后修改内部节点的关键码
(3)当叶子节点的关键码数低于填充因子(M/2),内部节点也低于
如果叶子节点的兄弟节点关键码数大于M/2,那么就从兄弟节点借,否则就和他的兄弟节点合并;然后修改内部节点的关键码;内部节点也类似处理
/** * B+树 * */ public class BPlusTree<K extends Comparable<K>, V> { // 根节点 protected BPlusNode<K, V> root; // 阶数,M值 protected int order; // 叶子节点的链表头 protected BPlusNode<K, V> head; // 树高 protected int height = 0; public BPlusNode<K, V> getHead() { return head; } public void setHead(BPlusNode<K, V> head) { this.head = head; } public BPlusNode<K, V> getRoot() { return root; } public void setRoot(BPlusNode<K, V> root) { this.root = root; } public int getOrder() { return order; } public void setOrder(int order) { this.order = order; } public void setHeight(int height) { this.height = height; } public int getHeight() { return height; } public V get(K key) { return root.get(key); } public V remove(K key) { return root.remove(key, this); } public void insertOrUpdate(K key, V value) { root.insertOrUpdate(key, value, this); } public BPlusTree(int order) { if (order < 3) { System.out.print("order must be greater than 2"); System.exit(0); } this.order = order; root = new BPlusNode<K, V>(true, true); head = root; } public void printBPlusTree() { this.root.printBPlusTree(0); } } class BPlusNode<K extends Comparable<K>, V> { // 是否为叶子节点 protected boolean isLeaf; // 是否为根节点 protected boolean isRoot; // 父节点 protected BPlusNode<K, V> parent; // 叶节点的前节点 protected BPlusNode<K, V> previous; // 叶节点的后节点 protected BPlusNode<K, V> next; // 节点的关键字列表 protected List<Entry<K, V>> entries; // 子节点列表 protected List<BPlusNode<K, V>> children; public BPlusNode(boolean isLeaf) { this.isLeaf = isLeaf; entries = new ArrayList(); if (!isLeaf) { children = new ArrayList(); } } public BPlusNode(boolean isLeaf, boolean isRoot) { this(isLeaf); this.isRoot = isRoot; } public V get(K key) { //如果是叶子节点 if (isLeaf) { int low = 0, high = entries.size() - 1, mid; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { return entries.get(mid).getValue(); } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } //未找到所要查询的对象 return null; } //如果不是叶子节点 //如果key小于节点最左边的key,沿第一个子节点继续搜索 if (key.compareTo(entries.get(0).getKey()) < 0) { return children.get(0).get(key); //如果key大于等于节点最右边的key,沿最后一个子节点继续搜索 } else if (key.compareTo(entries.get(entries.size() - 1).getKey()) >= 0) { return children.get(children.size() - 1).get(key); //否则沿比key大的前一个子节点继续搜索 } else { int low = 0, high = entries.size() - 1, mid = 0; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { return children.get(mid + 1).get(key); } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } return children.get(low).get(key); } } public void insertOrUpdate(K key, V value, BPlusTree<K, V> tree) { //如果是叶子节点 if (isLeaf) { //不需要分裂,直接插入或更新 if (contains(key) != -1 || entries.size() < tree.getOrder()) { insertOrUpdate(key, value); if (tree.getHeight() == 0) { tree.setHeight(1); } return; } //需要分裂 //分裂成左右两个节点 BPlusNode<K, V> left = new BPlusNode<K, V>(true); BPlusNode<K, V> right = new BPlusNode<K, V>(true); //设置链接 if (previous != null) { previous.next = left; left.previous = previous; } if (next != null) { next.previous = right; right.next = next; } if (previous == null) { tree.setHead(left); } left.next = right; right.previous = left; previous = null; next = null; //复制原节点关键字到分裂出来的新节点 copy2Nodes(key, value, left, right, tree); //如果不是根节点 if (parent != null) { //调整父子节点关系 int index = parent.children.indexOf(this); parent.children.remove(this); left.parent = parent; right.parent = parent; parent.children.add(index, left); parent.children.add(index + 1, right); parent.entries.add(index, right.entries.get(0)); entries = null; //删除当前节点的关键字信息 children = null; //删除当前节点的孩子节点引用 //父节点插入或更新关键字 parent.updateInsert(tree); parent = null; //删除当前节点的父节点引用 //如果是根节点 } else { isRoot = false; BPlusNode<K, V> parent = new BPlusNode<K, V>(false, true); tree.setRoot(parent); left.parent = parent; right.parent = parent; parent.children.add(left); parent.children.add(right); parent.entries.add(right.entries.get(0)); entries = null; children = null; } return; } //如果不是叶子节点 //如果key小于等于节点最左边的key,沿第一个子节点继续搜索 if (key.compareTo(entries.get(0).getKey()) < 0) { children.get(0).insertOrUpdate(key, value, tree); //如果key大于节点最右边的key,沿最后一个子节点继续搜索 } else if (key.compareTo(entries.get(entries.size() - 1).getKey()) >= 0) { children.get(children.size() - 1).insertOrUpdate(key, value, tree); //否则沿比key大的前一个子节点继续搜索 } else { int low = 0, high = entries.size() - 1, mid = 0; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { children.get(mid + 1).insertOrUpdate(key, value, tree); break; } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } if (low > high) { children.get(low).insertOrUpdate(key, value, tree); } } } private void copy2Nodes(K key, V value, BPlusNode<K, V> left, BPlusNode<K, V> right, BPlusTree<K, V> tree) { //左右两个节点关键字长度 int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2; boolean b = false;//用于记录新元素是否已经被插入 for (int i = 0; i < entries.size(); i++) { if (leftSize != 0) { leftSize--; if (!b && entries.get(i).getKey().compareTo(key) > 0) { left.entries.add(new SimpleEntry<K, V>(key, value)); b = true; i--; } else { left.entries.add(entries.get(i)); } } else { if (!b && entries.get(i).getKey().compareTo(key) > 0) { right.entries.add(new SimpleEntry<K, V>(key, value)); b = true; i--; } else { right.entries.add(entries.get(i)); } } } if (!b) { right.entries.add(new SimpleEntry<K, V>(key, value)); } } /** * 插入节点后中间节点的更新 */ protected void updateInsert(BPlusTree<K, V> tree) { //如果子节点数超出阶数,则需要分裂该节点 if (children.size() > tree.getOrder()) { //分裂成左右两个节点 BPlusNode<K, V> left = new BPlusNode<K, V>(false); BPlusNode<K, V> right = new BPlusNode<K, V>(false); //左右两个节点子节点的长度 int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2; int rightSize = (tree.getOrder() + 1) / 2; //复制子节点到分裂出来的新节点,并更新关键字 for (int i = 0; i < leftSize; i++) { left.children.add(children.get(i)); children.get(i).parent = left; } for (int i = 0; i < rightSize; i++) { right.children.add(children.get(leftSize + i)); children.get(leftSize + i).parent = right; } for (int i = 0; i < leftSize - 1; i++) { left.entries.add(entries.get(i)); } for (int i = 0; i < rightSize - 1; i++) { right.entries.add(entries.get(leftSize + i)); } //如果不是根节点 if (parent != null) { //调整父子节点关系 int index = parent.children.indexOf(this); parent.children.remove(this); left.parent = parent; right.parent = parent; parent.children.add(index, left); parent.children.add(index + 1, right); parent.entries.add(index, entries.get(leftSize - 1)); entries = null; children = null; //父节点更新关键字 parent.updateInsert(tree); parent = null; //如果是根节点 } else { isRoot = false; BPlusNode<K, V> parent = new BPlusNode<K, V>(false, true); tree.setRoot(parent); tree.setHeight(tree.getHeight() + 1); left.parent = parent; right.parent = parent; parent.children.add(left); parent.children.add(right); parent.entries.add(entries.get(leftSize - 1)); entries = null; children = null; } } } /** * 删除节点后中间节点的更新 */ protected void updateRemove(BPlusTree<K, V> tree) { // 如果子节点数小于M / 2或者小于2,则需要合并节点 if (children.size() < tree.getOrder() / 2 || children.size() < 2) { if (isRoot) { // 如果是根节点并且子节点数大于等于2,OK if (children.size() >= 2) return; // 否则与子节点合并 BPlusNode<K, V> root = children.get(0); tree.setRoot(root); tree.setHeight(tree.getHeight() - 1); root.parent = null; root.isRoot = true; entries = null; children = null; return; } //计算前后节点 int currIdx = parent.children.indexOf(this); int prevIdx = currIdx - 1; int nextIdx = currIdx + 1; BPlusNode<K, V> previous = null, next = null; if (prevIdx >= 0) { previous = parent.children.get(prevIdx); } if (nextIdx < parent.children.size()) { next = parent.children.get(nextIdx); } // 如果前节点子节点数大于M / 2并且大于2,则从其处借补 if (previous != null && previous.children.size() > tree.getOrder() / 2 && previous.children.size() > 2) { //前叶子节点末尾节点添加到首位 int idx = previous.children.size() - 1; BPlusNode<K, V> borrow = previous.children.get(idx); previous.children.remove(idx); borrow.parent = this; children.add(0, borrow); int preIndex = parent.children.indexOf(previous); entries.add(0, parent.entries.get(preIndex)); parent.entries.set(preIndex, previous.entries.remove(idx - 1)); return; } // 如果后节点子节点数大于M / 2并且大于2,则从其处借补 if (next != null && next.children.size() > tree.getOrder() / 2 && next.children.size() > 2) { //后叶子节点首位添加到末尾 BPlusNode<K, V> borrow = next.children.get(0); next.children.remove(0); borrow.parent = this; children.add(borrow); int preIndex = parent.children.indexOf(this); entries.add(parent.entries.get(preIndex)); parent.entries.set(preIndex, next.entries.remove(0)); return; } // 同前面节点合并 if (previous != null && (previous.children.size() <= tree.getOrder() / 2 || previous.children.size() <= 2)) { for (int i = 0; i < children.size(); i++) { previous.children.add(children.get(i)); } for (int i = 0; i < previous.children.size(); i++) { previous.children.get(i).parent = this; } int indexPre = parent.children.indexOf(previous); previous.entries.add(parent.entries.get(indexPre)); for (int i = 0; i < entries.size(); i++) { previous.entries.add(entries.get(i)); } children = previous.children; entries = previous.entries; //更新父节点的关键字列表 parent.children.remove(previous); previous.parent = null; previous.children = null; previous.entries = null; parent.entries.remove(parent.children.indexOf(this)); if ((!parent.isRoot && (parent.children.size() >= tree.getOrder() / 2 && parent.children.size() >= 2)) || parent.isRoot && parent.children.size() >= 2) { return; } parent.updateRemove(tree); return; } // 同后面节点合并 if (next != null && (next.children.size() <= tree.getOrder() / 2 || next.children.size() <= 2)) { for (int i = 0; i < next.children.size(); i++) { BPlusNode<K, V> child = next.children.get(i); children.add(child); child.parent = this; } int index = parent.children.indexOf(this); entries.add(parent.entries.get(index)); for (int i = 0; i < next.entries.size(); i++) { entries.add(next.entries.get(i)); } parent.children.remove(next); next.parent = null; next.children = null; next.entries = null; parent.entries.remove(parent.children.indexOf(this)); if ((!parent.isRoot && (parent.children.size() >= tree.getOrder() / 2 && parent.children.size() >= 2)) || parent.isRoot && parent.children.size() >= 2) { return; } parent.updateRemove(tree); return; } } } public V remove(K key, BPlusTree<K, V> tree) { //如果是叶子节点 if (isLeaf) { //如果不包含该关键字,则直接返回 if (contains(key) == -1) { return null; } //如果既是叶子节点又是根节点,直接删除 if (isRoot) { if (entries.size() == 1) { tree.setHeight(0); } return remove(key); } //如果关键字数大于M / 2,直接删除 if (entries.size() > tree.getOrder() / 2 && entries.size() > 2) { return remove(key); } //如果自身关键字数小于M / 2,并且前节点关键字数大于M / 2,则从其处借补 if (previous != null && previous.parent == parent && previous.entries.size() > tree.getOrder() / 2 && previous.entries.size() > 2) { //添加到首位 int size = previous.entries.size(); entries.add(0, previous.entries.remove(size - 1)); int index = parent.children.indexOf(previous); parent.entries.set(index, entries.get(0)); return remove(key); } //如果自身关键字数小于M / 2,并且后节点关键字数大于M / 2,则从其处借补 if (next != null && next.parent == parent && next.entries.size() > tree.getOrder() / 2 && next.entries.size() > 2) { entries.add(next.entries.remove(0)); int index = parent.children.indexOf(this); parent.entries.set(index, next.entries.get(0)); return remove(key); } //同前面节点合并 if (previous != null && previous.parent == parent && (previous.entries.size() <= tree.getOrder() / 2 || previous.entries.size() <= 2)) { V returnValue = remove(key); for (int i = 0; i < entries.size(); i++) { //将当前节点的关键字添加到前节点的末尾 previous.entries.add(entries.get(i)); } entries = previous.entries; parent.children.remove(previous); previous.parent = null; previous.entries = null; //更新链表 if (previous.previous != null) { BPlusNode<K, V> temp = previous; temp.previous.next = this; previous = temp.previous; temp.previous = null; temp.next = null; } else { tree.setHead(this); previous.next = null; previous = null; } parent.entries.remove(parent.children.indexOf(this)); if ((!parent.isRoot && (parent.children.size() >= tree.getOrder() / 2 && parent.children.size() >= 2)) || parent.isRoot && parent.children.size() >= 2) { return returnValue; } parent.updateRemove(tree); return returnValue; } //同后面节点合并 if (next != null && next.parent == parent && (next.entries.size() <= tree.getOrder() / 2 || next.entries.size() <= 2)) { V returnValue = remove(key); for (int i = 0; i < next.entries.size(); i++) { //从首位开始添加到末尾 entries.add(next.entries.get(i)); } next.parent = null; next.entries = null; parent.children.remove(next); //更新链表 if (next.next != null) { BPlusNode<K, V> temp = next; temp.next.previous = this; next = temp.next; temp.previous = null; temp.next = null; } else { next.previous = null; next = null; } //更新父节点的关键字列表 parent.entries.remove(parent.children.indexOf(this)); if ((!parent.isRoot && (parent.children.size() >= tree.getOrder() / 2 && parent.children.size() >= 2)) || parent.isRoot && parent.children.size() >= 2) { return returnValue; } parent.updateRemove(tree); return returnValue; } } /*如果不是叶子节点*/ //如果key小于等于节点最左边的key,沿第一个子节点继续搜索 if (key.compareTo(entries.get(0).getKey()) < 0) { return children.get(0).remove(key, tree); //如果key大于节点最右边的key,沿最后一个子节点继续搜索 } else if (key.compareTo(entries.get(entries.size() - 1).getKey()) >= 0) { return children.get(children.size() - 1).remove(key, tree); //否则沿比key大的前一个子节点继续搜索 } else { int low = 0, high = entries.size() - 1, mid = 0; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { return children.get(mid + 1).remove(key, tree); } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } return children.get(low).remove(key, tree); } } // 判断当前节点是否包含该关键字 protected int contains(K key) { int low = 0, high = entries.size() - 1, mid; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { return mid; } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } return -1; } // 插入到当前节点的关键字中 protected void insertOrUpdate(K key, V value) { //二叉查找,插入 int low = 0, high = entries.size() - 1, mid; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { entries.get(mid).setValue(value); break; } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } if (low > high) { entries.add(low, new SimpleEntry<K, V>(key, value)); } } // 删除节点 protected V remove(K key) { int low = 0, high = entries.size() - 1, mid; int comp; while (low <= high) { mid = (low + high) / 2; comp = entries.get(mid).getKey().compareTo(key); if (comp == 0) { return entries.remove(mid).getValue(); } else if (comp < 0) { low = mid + 1; } else { high = mid - 1; } } return null; } public String toString() { StringBuilder sb = new StringBuilder(); sb.append("isRoot: "); sb.append(isRoot); sb.append(", "); sb.append("isLeaf: "); sb.append(isLeaf); sb.append(", "); sb.append("keys: "); for (Entry<K, V> entry : entries) { sb.append(entry.getKey()); sb.append(", "); } sb.append(", "); return sb.toString(); } public void printBPlusTree(int index) { if (this.isLeaf) { System.out.print("层级:" + index + ",叶子节点,keys为: "); for (int i = 0; i < entries.size(); ++i) System.out.print(entries.get(i) + " "); System.out.println(); } else { System.out.print("层级:" + index + ",非叶子节点,keys为: "); for (int i = 0; i < entries.size(); ++i) System.out.print(entries.get(i) + " "); System.out.println(); for (int i = 0; i < children.size(); ++i) children.get(i).printBPlusTree(index + 1); } } }
B*-tree是B±tree的变体,在B+树的基础上(所有的叶子结点中包含了全部关键字的信息,及指向含有这些关键字记录的指针),B树中非根和非叶子结点再增加指向兄弟的指针;B树定义了非叶子结点关键字个数至少为(2/3)*M(M假设为满关键字2m-1),即块的最低使用率为2/3(代替B+树的1/2)(假设一个节点放在一个块)
暂略,稍后补上
暂略,稍后补上
假设是M阶,也就是最多M个孩子
(1)节点的关键字数量
B树:不小于M/2-1
B+树:不小于M/2
B树:不小于2/3
(2)节点的关键字数量和孩子节点数量是否相等
B树:不相等,孩子数=关键码+1
B+树:相等
B树:相等
(3)数据存放方式
B树:存在叶子和内部节点
B+树:只存在叶子,内部节点存放索引范围
B树:只存在叶子,内部节点存放索引范围
(4)链表指针
B树:不使用
B+树: 叶子节点使用
B树: 内部节点和叶子节点都使用
(5)他们的关系
B树:基础
B+树: 基于B树变形
B*树:基于B+树变形
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。