具体算法思想及应用介绍的文章网上有很多,这篇推荐给大家http://blog.csdn.net/hbhhww/article/details/8206846。
我就直接上自己的Java代码,之前写的时候也参考了一些其它语言的实现 。
/**
* <code>B-Tree</code> : <p />
* 假设B树的度为 m(m>=2),则B树满足如下要求:(参考算法导论)
* <br />
* (1)每个非根节点至少包含m-1个关键字,m个指向子节点的指针;至多包含2m-1个关键字,2m个指向子女的指针(叶子节点的子女为空)
* <br />
* (2)节点的所有key按非降序存放,假设节点的关键字分别为K[1], K[2] … K[n], 指向子女的指针分别为P[1], P[2]…P[n+1],
* 其中n为节点关键字的个数。则有:P[1] <= K[1] <= P[2] <= K[2] …..<= K[n] <= P[n+1] // 这里P[n]也指其指向的关键字
* <br />
* (3)若根节点非空,则根节点至少包含两个子女;
* <br />
* (4)所有的叶子节点都在同一层
* <p/>
*
*
* 查询:<br />
* 从<code>root</code>出发,对每个节点,找到大于或等于target关键字中最小的K[i]
* <br />
* (1)如果K[i]与target相等,则查找成功,返回一个标识true,和target下标i
* <br />
* (2)否则查找失败, 返回一个标识false,和一个下标指向其子节点位置,递归search新的子节点,如果是叶节点则表示不存在该关键字
*
* <p/>
* 插入:
* <br />
* B树的插入需要沿着搜索的路径从<code>root</code>一直到叶节点,根据B树的规则,每个节点的关键字个数在[m-1, 2m-1]之间,
* 当target要加入到某个叶子时,如果该叶子节点已经有2m-1个关键字,则再加入target就违反了B树的定义,
* 这时就需要对该叶子节点进行分裂,将叶子以中间节点为界,分成两个包含m-1个关键字的子节点,
* 同时把中间节点提升到该叶子的父节点中,如果这样使得父节点的关键字个数超过2m-1,
* 则要继续向上分裂,直到根节点,根节点的分裂会使得树加高一层
* <br />
* 关键:在下降的过程中,一旦遇到已满的节点(关键字个数为2m-1),就就对该节点进行分裂,这样就保证在叶子节点需要分裂时,
* 其父节点一定是非满的,从而不需要再向上回溯
*
* <p/>
* 删除:
* <br />
* B树的删除同样需要沿着搜索的路径从<code>root</code>一直到叶节点
* 1,根节点只有一个实例,且起子节点都只包含<code>minEntrySize</code>个实例时,树降高(树降高的唯一情形)
* <br />
* 2,如果在内部节点node中找到关键字key下标i,观察该节点的第i与i+i个子节点,在它俩中找到实例数大于<code>minEntrySize</code>的
* 记为tmpNode,中转tmpKey到当前node,在tmpNode中递归删除tmpKey,如果上述两个子节点实例数均不大于<code>minEntrySize</code>
* 则借当前key合并节点
* <br />
* 3,与插入时避免回溯的思想一样,保证(2)中合并节点时,借走父节点的 一个实例,而不须继续向上回溯
*/
public class BTree<K extends Comparable<K>, V> {
private static final int M = 4;
private int minEntrySize;
private int maxEntrySize;
private Node root;
private int size;
private int depth;
public BTree() {
this(M);
}
public BTree(int m) {
this.minEntrySize = m - 1;
this.maxEntrySize = (m * 2) - 1;
this.root = new Node(true);
}
/**
* @return 树中不存在当前key时返回null, 否则返回上次key保存的值
*/
public V put(K key, V value) {
if (root.isFull()) {// 创建新root
Node newRoot = new Node(false);
newRoot.children.add(root);
splitFullNode(root, newRoot, 0);
root = newRoot;
depth++;
}
return insert(root, key, value);
}
public V get(K key) {
return serach(root, key);
}
private V serach(Node current, K key) {
SerachResult sr = current.serach(key);
if (sr.serached) {
return current.entrys.get(sr.wudindex).value;
} else {
if (!current.isLeaf) {
return serach(current.children.get(sr.wudindex), key);
}
//递归到叶节点中仍没有查询到
return null;
}
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public int depth() {
return depth;
}
private V insert(Node current, K key, V value) {
if (current.isFull()) {
throw new UnsupportedOperationException("只能为非满节点插入实例...");
}
SerachResult sr = current.serach(key);
if (sr.serached) {
V oldValue = current.entrys.get(sr.wudindex).value;
current.entrys.get(sr.wudindex).value = value;
return oldValue;
}
if (!current.isLeaf) {
Node wudNode = current.children.get(sr.wudindex);
if (wudNode.isFull()) {
// 回溯过程中每遇到一个满实例节点就立刻分裂
// 保证在叶节点需要分裂时,其父节点一定是非满的,从而不需要再向上回溯
splitFullNode(wudNode, current, sr.wudindex);
if (key.compareTo(current.entrys.get(sr.wudindex).key) > 0) {//需要插入到分裂出的兄弟节点中
wudNode = current.children.get(sr.wudindex + 1);
}
}
return insert(wudNode, key, value);
} else {//所有节点都在叶节点加入
current.entrys.add(sr.wudindex, new Entry(key, value));
size++;
}
return null;
}
/**
* 将一个满实例节点分割, 中间实例提取至父节点中, 分隔出的部分((m - 1)个)实例放入新生成的兄弟节点中
* @param parent 父节点
* @param child 满实例节点
* @param i 满实例节点在父节点中的位置(提取出中间实例应在父节点中插入的位置)
*/
private void splitFullNode(Node fullNode, Node parent, int index) {
if (!fullNode.isFull()) {
throw new UnsupportedOperationException("不能对非满节点经行分裂!");
}
int middleIndex = (fullNode.entrys.size() - 1) / 2;
Entry middleEntry = fullNode.entrys.get(middleIndex);
//此处已保证parent加入新节点后仍满足B-tree特性
parent.entrys.add(index, middleEntry);
Node sibling = new Node(fullNode.isLeaf);
//采用向右分割
for (int i = maxEntrySize - 1; i > middleIndex; i--) {
sibling.entrys.add(0, fullNode.entrys.get(i));
fullNode.entrys.remove(i);
}
fullNode.entrys.remove(middleIndex);
parent.children.add(index + 1, sibling);
//如果分割的不是叶节点, 则fullNode的后半部分(m个)子节点将成为sibling的子节点
if (!fullNode.isLeaf) {
int cs = fullNode.children.size();
for (int i = cs - 1; i >= (cs / 2); i--) {
sibling.children.add(0, fullNode.children.get(i));
fullNode.children.remove(i);
}
}
}
private class Node {
List<Entry> entrys;
List<Node> children;
boolean isLeaf;
Node(boolean isLeaf) {
this.entrys = new ArrayList<Entry>();
this.children = new ArrayList<Node>();
this.isLeaf = isLeaf;
}
/**
* 在当前节点中查询
* @param key 搜索关键字
* @return {@link SerachResult}
*/
SerachResult serach(K key) {
int left = 0;
int right = entrys.size() - 1;
int middle = 0;
while (left <= right) {
middle = (left + right) / 2;
K ck = entrys.get(middle).key;
if (key.compareTo(ck) < 0) {
right = middle - 1;
} else if(key.compareTo(ck) > 0) {
left = middle + 1;
} else {
break;
}
}
boolean flag = true;
if (left > right) {//未找到
flag = false;
}
return new SerachResult(flag, left);
}
boolean isFull() {
return entrys.size() == maxEntrySize;
}
}
private class Entry {
K key;
V value;
Entry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public String toString() {
return "{key:" + key + ", value:" + value + "}";
}
}
/**
* 在节点中单次查询结果
*/
private class SerachResult {
/**
* 是否查询到
*/
boolean serached;
/**
* 查到时为在node实例集中的位置
* 未查到时表示在node子节点集对应节点中
*/
int wudindex;
SerachResult(boolean serached, int wudindex) {
this.serached = serached;
this.wudindex = wudindex;
}
}
}
我的实现里主要加入了泛型,提供了put(key, value); get(key); 两个操作。 大体上看就是一个HashMap的功能。
删除节点那部分的代码我这里没贴,因为实在太复杂也太长,我的代码也未必能让大家看的明白, 如果有兴趣的,自己动手实现一遍更好。