B树(Java)实现。

1. B树基本概念

1.1 B树背景

B树是用于在外存工作的平衡搜索树。

当数据比较大,无法全部存入内存时,需要将部分数据存在外存中,在需要的时候读入内存,修改之后又写回外存。由于外存的速度与内存有几个数量级的差别,所以节省在外存上花的时间,对搜索树的性能提高时最有效的。

最常见的外存就是磁盘。磁盘是快设备,也就是说磁盘的读写单位是以块为单位,一般地块大小从0.5k到4k。即使你只读取一个字节,磁盘也是将包含该字节的所有数据读取到硬盘中。而在磁盘读取过程中,最占用时间的是磁盘的寻道,也就是磁头在盘片上找到需要读取的块所在位置的时间,而在盘片上顺序读取数据的所花的时间是占比比较小的。

要减少外存上花的时间,就可以从减少读盘次数以及减少寻道时间着手。

B树采取的方法就是,就充分的利用盘块的空间,在一个盘块中尽可能多的存储信息,或者在连续的盘块地址上存储尽可能多的信息。在数据结构上的变化就是每个节点存储多个key信息以及包含多个子节点。增加节点的分支树,就可以使得这棵树的高度降低,比如高度为2(roo高度为0)分支1000的数,就以存储1000*1000个关键字信息,而二叉树j的高度就至少需要6*ln10。

如下图,M,D,J等称为key,也就是存入B树种的数据项。


图1 -B树示例(来自《算法导论》)

1.2 B树定义

(来自《算法导论》第三版)
B树T有如下性质:
      1. 每个节点x有如下属性:
      • x.n表示节点当前key的个数。
      • x中key满足:x.key1 <= x.key2<= x.key3 <= ....    <= x.keyx,n。也就是x中的key以非降序顺序排列。
      • x要么是叶子节点,要么是内部节点。
     2. 每个内部节点包含x.n + 1 个引用,指向x.n + 1个孩子节点。叶子节点没有孩子节点。
     3. x的key将孩子节点区分开,也就是满足:设ki 为 子树i中的任意key值,k 1 <= x.k 1 <= k 2 <= x.k 2 ....<= x.k x.n <= k x.n+1.
     4. 所有的叶子节点在同一层
                     5. 每个节点拥有的key以及孩子的数量有约束,设整数 t>=2 为最小度:
      • 除根节点外,每个节点必须有至少t-1个key,t个孩子。树不为空时,根节点至少有一个key。
      • 每个节点至多有2*t-1个key,每个内部节点至多有2*t个孩子。当一个节点有2*t-1个key时,称其为满节点。
t=2的B树称为2-3-4树,因为可以由2-3-4个孩子。

2. B树基本操作


2.1 查找

B树的查找和二叉树查找类似,首先在当前节点中查找,如果没有并且存在孩子节点,就递归的到可能存在该key的孩子节点中查找。
不同的是,B树节点有多个key,需要每个都比较,为了提高性能,可以使用二分法加速节点中的查找。

图2 -B树查找伪码(来自《算法导论》)

2.2 树的创建

B树创建很简单,将B树节点分配为一个空的叶子节点即可。

2.3 插入key

B树的插入只会在叶子节点中插入key,内部节点之后将插入操作传递到适当的子树中去,知道叶子节点中。
B树的插入需要考虑的一个问题就是当节点以满时,需要将该节点分裂成两个节点。
一个满的节点有2*t-1个key,内部节点有2*t 个孩子,分裂将其分成两个各有t-1个key,内部节点各t个孩子,多余的一个节点插入到父节点中,作为分裂之后两个节点的分割key。
如图,一个最小度为3的满节点,分裂之后,key C上移到父节点,成为分裂之后两个节点的分割key。分裂之后,父节点多了一个key和一个孩子。

图3 -B树分裂示例
为了是插入操作可以顺树根到叶子节点一遍完成,而不需要回溯到父节点中,需要做如下操作:
    1. 若是根节点,则生成一个新的根节点,原根节点作为新根节点的第一个孩子,并对该孩子进行分裂操作。
    2. 若是内部节点,每次向适当孩子传递操作时,都需要检查该子树是否已满,若满则进行该子树,再将插入操作传递到适当的子树中。
    3. 若是叶子节点,则在适当的位置插入需要插入的key
如此,则传递到需要操作的叶子节点都是不满的,都可以直接进行插入操作。并且可以看到,B树的高度增加只有在根节点已满时,分裂根节点增加高度,所以使得所有叶子节点的高度一样。

插入示例:

图4 -B树插入示例(来自《算法导论》)
该B树最下度为3,所以节点最多有5个key,最少有2个key。
  • b) 插入B:孩子未满,直接插入
  • c) 插入Q:孩子已满,分裂子树,key T上移到父节点中,然后在将Q插入到适当的孩子中
  • d) 插入L:root已满,生成新root节点,分裂老root节点,在适当子树中插入适当孩子中
  • e) 插入F:孩子已满,分裂子树,key C上移到父节点,在适当节点中插入Q

2.4 删除key

删除的时候,当key存在的节点的key数量等于t-1时,再删除就会破坏B树属性,所以为了不回溯,在删除操作传递到子树中之前,需要检查子树key的数量。
删除操作步骤如下:
  1. 待删除key如果在当前节点中,转2,否则转8
  2. 当前节点是叶子,直接删除,完成删除操作。否则转3
  3. 待删除key分割的子树中,前一棵子树key的数量大于t-1,转4,否则转5.
  4. 从前一颗子树中删除该子树根节点中最大的key,将该key替换当前节点中待删除key,完成删除操作。
  5. 待删除key分割的子树中,后一棵子树的key数量打于t-1,转6,否则转7.
  6. 从后一颗子树中的根结点中删除该节点最小的key,用该key替换待删除key,完成删除。
  7. 合并该节点分割的两个子树,并从合并之后的子树中删除待删除key。
  8. 找到key可能存在的子树Tn,转9
  9. 该子树前一颗子树Tn-1的根节点key数量大于t-1,转10,否则转12
  10. 将Tn-1中最大的key替换当前节点中适当的key,并将被替换的key插入到Tn中,转11
  11. 将Tn-1中最后一个孩子,移动到Tn中适当的位置,将删除操作传递到Tn中。
  12. Tn的后一颗子树Tn+1的根节点key数量大于t-1,转13,否则转?
  13. 将Tn+1中最小的key替换当前节点,并将被替换的key插入到Tn+1中,转14
  14. 将Tn+1中最小的子树移动到Tn中,将删除操作传递Tn中。
删除中,可能会出现根节点没有key的情况,所以删除结束之后需要检查根节点,若发生这种情况,需要将根节点更新为原根节点的唯一的一颗子树。
示例:
  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
B树是一种自平衡的搜索树,常用于据库和文件系统等数据结构中。在Java中,我们可以使用以下方式实现B树: 1. 定义B树节点类: ``` public class BTreeNode { public int[] keys; // 节点存储的关键字组 public int t; // 最小度 public BTreeNode[] children; // 子节点组 public int n; // 节点关键字个 public boolean leaf; // 是否为叶子节点 public BTreeNode(int t, boolean leaf) { this.t = t; this.leaf = leaf; this.keys = new int[2 * t - 1]; this.children = new BTreeNode[2 * t]; this.n = 0; } } ``` 2. 定义B树类: ``` public class BTree { private BTreeNode root; // B树根节点 private int t; // 最小度 public BTree(int t) { this.t = t; this.root = null; } // 插入关键字 public void insert(int key) { if (root == null) { root = new BTreeNode(t, true); root.keys[0] = key; root.n = 1; } else { if (root.n == 2 * t - 1) { // 根节点已满,需要分裂 BTreeNode newRoot = new BTreeNode(t, false); newRoot.children[0] = root; splitChild(newRoot, 0, root); int i = 0; if (newRoot.keys[0] < key) { i++; } insertNonFull(newRoot.children[i], key); root = newRoot; } else { insertNonFull(root, key); } } } // 插入关键字(非满节点) private void insertNonFull(BTreeNode node, int key) { int i = node.n - 1; if (node.leaf) { while (i >= 0 && node.keys[i] > key) { node.keys[i + 1] = node.keys[i]; i--; } node.keys[i + 1] = key; node.n++; } else { while (i >= 0 && node.keys[i] > key) { i--; } i++; if (node.children[i].n == 2 * t - 1) { // 子节点已满,需要分裂 splitChild(node, i, node.children[i]); if (node.keys[i] < key) { i++; } } insertNonFull(node.children[i], key); } } // 分裂子节点 private void splitChild(BTreeNode parent, int i, BTreeNode node) { BTreeNode newNode = new BTreeNode(t, node.leaf); newNode.n = t - 1; for (int j = 0; j < t - 1; j++) { newNode.keys[j] = node.keys[j + t]; } if (!node.leaf) { for (int j = 0; j < t; j++) { newNode.children[j] = node.children[j + t]; } } node.n = t - 1; for (int j = parent.n; j >= i + 1; j--) { parent.children[j + 1] = parent.children[j]; } parent.children[i + 1] = newNode; for (int j = parent.n - 1; j >= i; j--) { parent.keys[j + 1] = parent.keys[j]; } parent.keys[i] = node.keys[t - 1]; parent.n++; } // 查找关键字 public boolean search(int key) { return search(root, key); } private boolean search(BTreeNode node, int key) { int i = 0; while (i < node.n && key > node.keys[i]) { i++; } if (i < node.n && key == node.keys[i]) { return true; } if (node.leaf) { return false; } return search(node.children[i], key); } } ``` 3. 测试B树: ``` public class Test { public static void main(String[] args) { BTree bTree = new BTree(3); bTree.insert(1); bTree.insert(3); bTree.insert(7); bTree.insert(10); bTree.insert(11); bTree.insert(13); bTree.insert(14); bTree.insert(15); bTree.insert(18); bTree.insert(16); bTree.insert(19); bTree.insert(24); bTree.insert(25); bTree.insert(26); bTree.insert(21); bTree.insert(4); bTree.insert(5); bTree.insert(20); bTree.insert(22); bTree.insert(2); bTree.insert(17); bTree.insert(12); System.out.println(bTree.search(15)); // true System.out.println(bTree.search(27)); // false } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值