B树类似于红黑树,不同于红黑树的地方在于,B树的一个节点,不是保存一个key值,而是保存多个key。并且这些key值是按从小到大顺序保存。另外一个不同于红黑树的地方在于,红黑树的节点分支最多有两个,但是B树可以有多个,至于有多少个,跟保存的key值有关系。而且B树的各个分支的高度都是一样的。
一个简单的B树
1.B树的每个节点x需要保存如下信息:
a.节点中key的数量,记做N[x]
b.N[x]个key的值,并且按照从小到大的顺序保存
Key1<Key2< Key3<…..< KeyN[x]
c. x节点是否是叶节点
2.每个节点包含了N[x]+1个子节点指针,指向他的子节点,叶节点没有该信息
3.子节点中的key与父节点中的key的关系,即父节点中k1和k2间的子节点的key的大小在k1和k2之间。
4.所有的叶节点的高度相同
5.每个节点保存的key的数量有上下限限制。
B-Tree有一个最新维度的整型变量t,且t>=2.
除了根节点外,其他节点中key的数量size 需满足如下关系
t-1<=size<=2t-1
所有内部节点的子树的范围是t到2t
/** * B树节点数据结构定义 B树性质: 1.除了根节点,其他节点中key的数量size,满足条件 t-1<=size<=2t-1 * 2.节点内部key是按照顺序存储 3.子节点的key必须在父节点key的范围之内 B树节点需要保存的数据: 1.所有的key 2.key的数量 * 3.子树节点 4.子树节点与key的关系 5.是否叶节点 * * @author root * @version [版本号, 2014-10-30] * @see [相关类/方法] * @since [产品/模块版本] */ public class BTreeNode { // 是否是叶节点 private boolean isLeaf; // key的数量 private volatile int size; private int t; /** * 因为B树在进行插入或者删除操作时 内部节点的key经常进行节点拆分,或者进行节点合并 所以保存key的数据结构最好是用爽端链表 首尾操作比较方便 */ private int[] keyList; // LinkedList<BTreeKey> keyList = new LinkedList<BTreeKey>(); /** * 子节点的保存 通常情况下,进行节点插入的时候,对full节点进行拆分时,伴随着key的迁移,对应的子树也会被迁移 * */ private BTreeNode[] subTreeNodeList; private BTreeNode parentNode; public BTreeNode(int t) { this.isLeaf = true; size = 0; this.keyList = new int[this.t - 1]; this.t = t; this.subTreeNodeList = new BTreeNode[this.t - 1]; } public boolean isLeaf() { return isLeaf; } public void setLeaf(boolean isLeaf) { this.isLeaf = isLeaf; } public int getSize() { return size; } public boolean isFull() { return size == 2 * t - 1; } public int getT() { return this.t; } public BTreeNode[] getSubTreeNodeList() { return subTreeNodeList; } public void setSubTreeNodeList(BTreeNode[] subTreeNodeList) { this.subTreeNodeList = subTreeNodeList; } public int[] getKeyList() { return keyList; } public void setKeyList(int[] keyList) { this.keyList = keyList; this.size = keyList.length; } public BTreeNode getParentNode() { return parentNode; } public void setParentNode(BTreeNode parentNode) { this.parentNode = parentNode; } /** * 在一个节点中寻找key,找打则返回非负数 没找到,如果是叶节点,返回-1 如果是内部节点,继续寻找子节点 * * @param key * @return * @see [类、类#方法、类#成员] */ public SearchResult searchKey(int key) { // 节点不存在任何key if (size == 0) { return null; } int index = 0; for (index = 0; index < size; index++) { int value = keyList[index]; if (value >= key) { break; } } int current = keyList[index]; if (current == key) { return new SearchResult(this, index); } // 叶节点没有子树,没有找到 if (isLeaf) { return null; } return subTreeNodeList[index].searchKey(key); } class SearchResult { private BTreeNode node; private int index; public SearchResult(BTreeNode node, int index) { this.node = node; this.index = index; } public BTreeNode getNode() { return node; } public int getIndex() { return index; } } /** * <一句话功能简述> <功能详细描述> * * @param parent * 父节点 * @param index * parent的第index个子节点是 这个节点 * @param toChild * 新创建的节点 * @return * @see [类、类#方法、类#成员] */ public void splitBTreeKey(BTreeNode parent, int index, BTreeNode toChild) { int middleKey = keyList[t - 1]; int[] moveBTreeKey = new int[t - 1]; for (int i = t; i < keyList.length; i++) { moveBTreeKey[i - t] = keyList[i]; keyList[i] = Integer.MAX_VALUE; } // 迁移key,设置size toChild.setKeyList(moveBTreeKey); BTreeNode[] toChildSubNode = new BTreeNode[t]; if (!isLeaf) { // 移动子节点 for (int i = 0; i < t; i++) { BTreeNode subNode = subTreeNodeList[i + t]; toChildSubNode[i] = subNode; subTreeNodeList[i + t] = null; } toChild.setSubTreeNodeList(toChildSubNode); } this.size = t - 1; // 父节点调整,将中间key middleKey提升到父节点 int[] newKeyList = new int[parent.getSize() + 1]; int[] parentKeys = parent.getKeyList(); for (int i = 0; i < parentKeys.length; i++) { if (i < index) { newKeyList[i] = parentKeys[i]; } else if (i >= index) { newKeyList[i + 1] = parentKeys[i]; } } newKeyList[index] = middleKey; BTreeNode[] newSuBTreeNode = new BTreeNode[size + 1]; BTreeNode[] parentNodes = parent.getSubTreeNodeList(); for (int i = 0; i < parentNodes.length; i++) { if (i <= index) { newSuBTreeNode[i] = parentNodes[i]; } else if (i > index) { newSuBTreeNode[i + 1] = parentNodes[i]; } } newSuBTreeNode[index + 1] = toChild; parent.setSubTreeNodeList(newSuBTreeNode); parent.setKeyList(newKeyList); } public boolean isSizeMin() { return size == t - 1; } /** * 删除key,在删除key的同时,需要移除一个子树,direction决定了移除哪个子树 direction 0 是左 1是右 <功能详细描述> * * @param index * @param direction * @see [类、类#方法、类#成员] */ public void delete(int index, int direction) { // 删除节点中的index位置的key int[] newKeyList = new int[size - 1]; for (int i = 0; i < size - 1; i++) { if (i < index) { newKeyList[i] = keyList[i]; } else if (i > index) { newKeyList[i] = keyList[i + 1]; } } if (direction == 1) { index++; } // 删除key,对应的右子树的指针删除 BTreeNode[] newSubNodeLis = new BTreeNode[size]; for (int i = 0; i < size; i++) { if (i < index) { newSubNodeLis[i] = subTreeNodeList[i]; } else if (i > index) { newSubNodeLis[i] = subTreeNodeList[i + 1]; } } subTreeNodeList = newSubNodeLis; keyList = newKeyList; } public void deleteKForLeaf(int index, int k) { if (isLeaf && index > -1) { for (int i = index; i < size; i++) { keyList[i] = keyList[i + 1]; } keyList[size - 1] = Integer.MAX_VALUE; size--; } } public void replace(int index, int desKey) { if (index > -1 && index < size) { if (index == 0 && keyList[index + 1] > desKey) { keyList[index] = desKey; } else if (index == size - 1 && keyList[index - 1] < desKey) { keyList[index] = desKey; } else if (index > 0 && index < size - 1 && keyList[index + 1] > desKey && keyList[index - 1] < desKey) { keyList[index] = desKey; } } } public int contains(int k) { int index = -1; for (int i = 0; i < size; i++) { if (keyList[i] == k) { index = i; } } return index; } /** * 检索k,进入下一级节点 <功能详细描述> * * @param k * @return * @see [类、类#方法、类#成员] */ public int next(int k) { int index = -1; for (int i = 0; i < size; i++) { if (keyList[i] > k) { index = i; break; } } if (keyList[size - 1] < k) { return size; } return index; } /** * 在index位置,插入k 并且插入子节点 newSubNode 如果direction=0,newSubNode插入在index * 如果direction=1,newSubNode插入在index+1 <功能详细描述> * * @param index * @param k * @param newSubNode * @param direction * @see [类、类#方法、类#成员] */ public void add(int index, int k, BTreeNode newSubNode, int direction) { int[] newKeyListOfSubNode = new int[keyList.length + 1]; BTreeNode[] newSubNodeListOfSubNode = new BTreeNode[subTreeNodeList.length + 1]; for (int i = 0; i < keyList.length + 1; i++) { if (i < index) { newKeyListOfSubNode[i] = keyList[i]; } else if (i == index) { newKeyListOfSubNode[i] = k; } else { newKeyListOfSubNode[i] = keyList[i - 1]; } } // subNode 的 subNode if (direction == 1) { index++; } for (int i = 0; i < subTreeNodeList.length + 1; i++) { if (i < index) { newSubNodeListOfSubNode[i] = subTreeNodeList[i]; } else if (i == 0) { // 左兄弟节点的子树移动到右边 newSubNodeListOfSubNode[i] = newSubNode; } else { newSubNodeListOfSubNode[i] = subTreeNodeList[i - 1]; } } keyList = newKeyListOfSubNode; subTreeNodeList = newSubNodeListOfSubNode; } }
public class BTreeOperations { /** * 在某个B 树上查找key 返回节点,已经index * * @param node * @param k * @return * @see [类、类#方法、类#成员] */ public SearchResult searchBTree(BTreeNode node, int k) { return node.searchKey(k); } /** * 将一个key插入到B-tree 肯定是将key插入到一个已经存在的node中 但是不要忘记B-tree关于key数量的限制,t-1~2t-1 * 所以在一个node 中key数量已经==2t-1的情况下,需要对它进行拆分 * * @param k * @return * @see [类、类#方法、类#成员] */ public BTreeNode insertBTree(BTree bt, int k) { BTreeNode root = bt.getRoot(); if (root.isFull()) { /** * 根节点满了 申请新的节点s 将s的子树指向root 对root进行拆分 拆分完成后进行插入操作 */ BTreeNode s = new BTreeNode(bt.getT()); s.getSubTreeNodeList()[0] = root; spiltChildBTree(s, 0, root); insertBTreeNonFull(s, k); } else { insertBTreeNonFull(root, k); } return null; } /** * key插入到叶节点中 如果是叶节点,那么将key从后开始移动,将key插入到合适的位置 如果是内部节点,遍历key,找到合适的子节点 * 如果子节点是full,那么对子节点进行拆分 拆分完成后,重新调用该方法 * * @param currentNode * @param k * @see [类、类#方法、类#成员] */ public void insertBTreeNonFull(BTreeNode currentNode, int k) { int size = currentNode.getSize(); int[] currKeys = currentNode.getKeyList(); if (currentNode.isLeaf()) { // 将key放到叶节点中 // key后移 int[] newBTreKeys = new int[size + 1]; for (int i = size - 1; i >= 0; i--) { if (currKeys[i] > k || (currKeys[i] < k && currKeys[i + 1] > k)) { newBTreKeys[i + 1] = k; newBTreKeys[i] = currKeys[i]; } else { newBTreKeys[i] = currKeys[i]; } } currentNode.setKeyList(newBTreKeys); } else { int index = 0; for (int i = size - 1; i >= 0; i--) { if (currKeys[i] < k) { index = i; break; } } // 第index个子节点 index++; BTreeNode subBTreeNode = currentNode.getSubTreeNodeList()[index]; if (subBTreeNode.isFull()) { spiltChildBTree(currentNode, index, subBTreeNode); if (k > currentNode.getKeyList()[index]) { index++; } insertBTreeNonFull(currentNode.getSubTreeNodeList()[index], k); } } } /** * 对节点进行拆分 也就是将一个已经是full 的节点进行拆分,用中间的key进行分解,即索引是t-1, 拆分成 2个 t-1的节点, * 并将这个中间key提升到父节点中,同时将key的左右指向这两个子节点 * * @param parent * @param index * @param child * @see [类、类#方法、类#成员] */ public void spiltChildBTree(BTreeNode parent, int index, BTreeNode child) { BTreeNode newNode = new BTreeNode(child.getT()); newNode.setLeaf(child.isLeaf()); child.splitBTreeKey(parent, index, newNode); } public void delete(BTreeNode root, int k) { SearchResult result = root.searchKey(k); // k不在这个b-tree中,不需要进入算法 if (result == null) { return; } // root deleteFromBtree(root, k); } /** * 分析: 这个key可能存在于内部节点,也有可能在叶节点,所有删除操作在任何节点都有可能发生 * 在不做任何判断的情况下,在一个节点内,将一个key删除,我们考虑一下,是否违反b-tree的性质 * 有可能违反:除根节点外,其他节点的key数量不能小于t-1 所以从节点内删除一个key的时候必须保证节点内key的数量删除之前必须大于t-1 * case: 1.k在节点x中,且x是leaf,直接删除k 2.k在节点x中,且x是内部节点,按照如下流程进行: * a.在x节点中,假设k的前继子树是y,y的key的数量至少有t,那么就可以将y中最大的k,我们记作ky=maxkey(y); * 在x中用ky代替k,然后重新进入算法,入参是节点y,删除ky. * b.如果前继子树key数量<t,那么查看k的后续子树的z,如果z的key的数量至少有t,那么就可以将z中最小的kz=minKey(z), * 用kz代替k,重新进入算法,入参:节点z,删除kz, c.如果k的前继,后继子树的key数量都是t-1,那么将z * merge到y,x节点中的k也将下移到y,x将失去k和指向z的子树指针,然后从y中将k删除 * * 3.如果x中不包含k,那么继续在子树中寻找,假设y肯定在这个B-tree中。 * 假设k的下一个搜索路径在在x的第i个子节点Ci[x]中,如果Ci[x]key的size=t-1,则进入我们这个case3 * 我们用w表示Ci[x],size(w)=t-1,w肯定是有兄弟节点的。i为第i个子节点 * a.如果w节点的相邻节点A是右节点,并且A的key数量>t-1,将A中最小的key,记作min(A)从A中删除, * 然后将min(A)replace他们父节点的第i个key,然后将第i个key转移到w * 同时w新增的子节点是A的第一个子节点。现在明确了w和A的key和子树的更新关系。更新w和A的key和子树即可 * * 没有右兄弟节点,查看左兄弟节点B,并且B的key数量>t-1,将B中最大的key,记作max(B)从B中删除, * 然后将max(B)replace他们父节点的第i-1个key,然后将第i-1个key转移到w * 同时w新增的子节点是B的最后一个子节点。现在明确了w和B的key和子树的更新关系。更新w和B的key和子树即可 * b.如果w的相邻的兄弟节点的key数量都是t-1,那么合并两个兄弟节点,并且将对应的x中第i个key从父节点下移到merge的中间节点。 * 重新进入算法,参数:节点w,需要删除的k * * * 这个算法的基本思想就是在进入到下一级的子节点时候,保证子节点的key数量至少是t, * 这是为了在删除key的时候,保证每个node删除节点后,或者节点中的一个key下移后,节点key的数量依然不小于t-1 * * @param tree * @param k * @see [类、类#方法、类#成员] */ public void deleteFromBtree(BTreeNode node, int k) { // case1:包含k,并且node是叶节点 if (node.contains(k) != -1 && node.isLeaf()) { // 直接删除k int index = node.contains(k); node.deleteKForLeaf(index, k); return; } // case2: int indexOfK = node.contains(k); if (indexOfK != -1 && !node.isLeaf()) { // k后继节点 BTreeNode successor = node.getSubTreeNodeList()[indexOfK + 1]; // k前继节点 BTreeNode preNode = node.getSubTreeNodeList()[indexOfK]; if (!preNode.isSizeMin()) { int maxKey = preNode.getKeyList()[preNode.getSize() - 1]; node.replace(indexOfK, maxKey); preNode.replace(preNode.getSize() - 1, k); } else if (!successor.isSizeMin()) { int minKey = successor.getKeyList()[0]; node.replace(indexOfK, minKey); successor.replace(0, k); } else { merge(preNode, successor, k); // 父节点删除key,删除子树 node.delete(indexOfK, 1); } indexOfK = node.contains(k); BTreeNode newSubNode = node.getSubTreeNodeList()[indexOfK]; deleteFromBtree(newSubNode, k); } // 获取下一级搜索的节点 int subNodeIndex = node.next(k); BTreeNode[] subNodesList = node.getSubTreeNodeList(); BTreeNode subNode = subNodesList[subNodeIndex]; if (subNode.isSizeMin()) { // 进入case3 // 一定有左兄弟 if (subNodeIndex > 0) { /** * 最后一个子树,所有没有右兄弟节点 获取左兄弟节点 */ int[] nodeKeys = node.getKeyList(); BTreeNode leftNode = subNodesList[subNodeIndex - 1]; if (!leftNode.isSizeMin()) { int leftNodeMaxKey = leftNode.getKeyList()[leftNode.getSize() - 1]; BTreeNode leftNodeSubNode = leftNode.getSubTreeNodeList()[leftNode.getSize()]; int key = nodeKeys[subNodeIndex]; // 将左节点的最大key上移到父节点 node.getKeyList()[subNodeIndex] = leftNodeMaxKey; // node节点的key下移到subNode节点 subNode.add(0, key, leftNodeSubNode, 0); // 左兄弟节点删除移动的key和移动的子树 leftNode.delete(leftNode.getSize(), 1); } else { merge(leftNode, subNode, nodeKeys[subNodeIndex]); // 父节点删除key,删除子树 node.delete(subNodeIndex, 1); } } else if (subNodeIndex == 0) { // 只有有右兄弟节点 BTreeNode[] nodeSubNodes = node.getSubTreeNodeList(); int[] nodeKeys = node.getKeyList(); BTreeNode rightNode = nodeSubNodes[subNodeIndex + 1]; if (!rightNode.isSizeMin()) { int rightNodeMinKey = rightNode.getKeyList()[rightNode.getSize() - 1]; BTreeNode rightNodeSubNode = rightNode.getSubTreeNodeList()[rightNode.getSize()]; int key = nodeKeys[subNodeIndex]; // 将右节点的最小key上移到父节点 node.getKeyList()[subNodeIndex] = rightNodeMinKey; // node节点的key下移到subNode节点 subNode.add(subNode.getSize(), key, rightNodeSubNode, 1); // 右兄弟节点删除移动的key和移动的子树 rightNode.delete(0, 0); } else { // 两个兄弟节点进行merge,右兄弟merge到左兄弟 merge(subNode, rightNode, nodeKeys[subNodeIndex]); // 父节点删除key,删除子树 node.delete(subNodeIndex, 1); } } int newIndex = node.next(k); deleteFromBtree(node.getSubTreeNodeList()[newIndex], k); } } /** * 将右节点合并到左 并且在新的节点insert key <功能详细描述> * * @param lefNode * @param right * @param k * @see [类、类#方法、类#成员] */ private void merge(BTreeNode leftNode, BTreeNode rightNode, int k) { int t = leftNode.getT(); int[] mergekeyList = new int[2 * t - 1]; int[] leftKeys = leftNode.getKeyList(); int[] rightKeys = rightNode.getKeyList(); // key合并 for (int i = 0; i < mergekeyList.length; i++) { if (i < t - 1) { mergekeyList[i] = leftKeys[i]; } else if (i == t - 1) { mergekeyList[i] = k; } else { mergekeyList[i] = rightKeys[i - t + 2]; } } // 子树合并 BTreeNode[] mergeBTreeNodes = new BTreeNode[2 * t]; BTreeNode[] leftSubNodes = leftNode.getSubTreeNodeList(); BTreeNode[] rightSubNode = rightNode.getSubTreeNodeList(); for (int i = 0; i < mergeBTreeNodes.length; i++) { if (i < t) { mergeBTreeNodes[i] = leftSubNodes[i]; } else { mergeBTreeNodes[i] = rightSubNode[i - t]; } } leftNode.setKeyList(mergekeyList); leftNode.setSubTreeNodeList(mergeBTreeNodes); } }