定義
一顆b樹T是具有一下性質(zhì)的有根樹
- 每個節(jié)點x均有如下屬性:
- n表示存儲在該節(jié)點的關(guān)鍵字個數(shù)
- n個關(guān)鍵字本身key1、key2……keyn以非降序存放,即key1 <= key2 <= …… <= keyn
- 一個leaf布爾值表示該節(jié)點是否為葉節(jié)點
- 每個內(nèi)部節(jié)點包含了n+1個孩子糟红,葉節(jié)點沒有孩子
- 關(guān)鍵字keyi對存儲在各子樹中的關(guān)鍵字范圍加以分割:即比keyi小的元素在其左子樹,比keyi大的元素在其右子樹
- 每個葉節(jié)點具有相同的深度
- 每個節(jié)點包含的關(guān)鍵字個數(shù)有上界與下界。我們定義B樹的最小度數(shù)為t合武,則除根節(jié)點外的每個節(jié)點至少有t-1個關(guān)鍵字前硫,每個節(jié)點最多有2t-1個關(guān)鍵字胞得。
Paste_Image.png
作用
B樹是為磁盤或其他直接存取的輔助存儲設(shè)備而設(shè)計的一種平衡搜索樹。
主存對磁盤的讀取是以頁面為單位的屹电,一個頁面是連續(xù)的存儲單元組成阶剑,一旦從主存讀取不到,就會從磁盤替換頁面危号,同時一次磁盤I/O耗時很多牧愁。因為節(jié)點大,高度小的樹進(jìn)行的磁盤I/0次數(shù)就少外莲,所以總磁盤I/O耗時少猪半。
實現(xiàn)
用java實現(xiàn)了B樹的增刪改查
實現(xiàn)代碼:
public class BTree<E extends Comparable<E>, F> {
private final int t; //度數(shù)
private Node<E, F> T = null; //根節(jié)點
private int height = 0; //數(shù)的高度
public int getHeight() {
return height;
}
public BTree(int t) {
this.t= t;
}
@Override
public String toString() {
if (T == null) {
return null;
}
return T.toString();
}
public F search(E e) {
if (T == null) {
return null;
}
int i = 0;
Node<E, F> p = T;
while (true) {
while (i < p.n && e.compareTo(p.getKey(i)) > 0) { //可改進(jìn)為折半查找
i++;
}
if (i != p.n && p.getKey(i).compareTo(e) == 0) {
return p.getValue(i);
}
if (p.isLeaf) {
return null;
}
p = p.c[i];
i = 0;
}
}
public void update(E e, F f) {
if (T == null) {
return;
}
int i = 0;
Node<E, F> p = T;
while (true) {
while (i < p.n && e.compareTo(p.getKey(i)) > 0) { //可改進(jìn)為折半查找
i++;
}
if (i != p.n && p.getKey(i).compareTo(e) == 0) {
p.kavs[i].value = f;
return;
}
if (p.isLeaf) {
return;
}
p = p.c[i];
i = 0;
}
}
private void insertWhenRootIsNulll(E e, F f) {
T = new Node<E, F>(true);
T.add(e, f);
T.tier=0;
height = 1;
}
public void insert(E e, F f) {
if (T == null) {
insertWhenRootIsNulll(e, f);
return;
}
Node<E, F> father = null;
Node<E, F> node = T;
int i = 0;
while (true) {
if (node.n == 2*t-1) {
node=nodeSplit(father, node, e);
}
if (node.isLeaf) {
node.add(e, f);
return;
}
while (i < node.n && e.compareTo(node.getKey(i)) > 0) { //可改進(jìn)為折半查找
i++;
}
father = node;
node = node.c[i];
i = 0;
}
}
private Node<E, F> nodeSplit(Node<E, F> father, Node<E, F> node, E e) {
int mid = t-1;
//System.out.println("mid = " + mid);
Node<E, F> left = new Node<E, F>(node.isLeaf);
left.copyKeyAndValues(node, 0, mid-1);
left.tier = node.tier;
//System.out.println("left" + 0 + " " + (mid-1));
Node<E, F> right = new Node<E, F>(node.isLeaf);
right.copyKeyAndValues(node, mid+1, node.n-1);
right.tier = node.tier;
//System.out.println("right" + (mid+1) + " " + (node.n-1));
if (father == null) { //說明分裂的是根節(jié)點
father = new Node<E, F>(false);
father.tier = node.tier+1;
height++;
T = father;
}
father.deleteChild(node);
int index = father.add(node.kavs[mid]);
father.addChild(left, index);
father.addChild(right, index+1);
left.copyChildren(node, 0, mid);
right.copyChildren(node, mid+1, node.n);
return e.compareTo(node.kavs[mid].key) > 0? right:left;
}
public F delete(E e) { //問題很多,還需要修改
if (T == null) {
return null;
}
return seekAndDeleteNode(e, null, T, -1);
}
private F seekAndDeleteNode(E key, Node<E, F> father, Node<E, F> p, int j) {
int i = 0;
while (true) {
if (p.n == t-1) {
p = nodeDeleteHandle(father, p, j);
}
while (i < p.n && key.compareTo(p.getKey(i)) > 0) { //可改進(jìn)為折半查找
i++;
}
if (i != p.n && p.getKey(i).compareTo(key) == 0) {
break;
}
if (p.isLeaf) {
return null;
}
father = p;
p = p.c[i];
j=i;
i = 0;
}
return nodeDelete(p, i);
}
private void whenRootshouldBeNull() {
if (T.n == 0) {
T = null;
height = 0;
}
}
//調(diào)用時 p.n>t-1 || p == T
private F nodeDelete(Node<E, F> p, int i) {
if (p.isLeaf) {
//只有在T為葉子節(jié)點的時候T才可能為空
F value = p.delete(i).value;
whenRootshouldBeNull();
return value;
} else {
//將兩個孩子節(jié)點與要刪除的值合并,再刪除那個合并點中的那個值
if (p.c[i].n == t-1 && p.c[i+1].n == t-1) {
Node<E, F> leftchild = p.deleteChild(i);
Node<E, F> rightchild = p.deleteChild(i);
KeyAndValue<E, F> mid = p.delete(i);
Node<E, F> newNode = nodeUnion(leftchild, rightchild, mid);
p.addChild(newNode, i);
if (p.n == 0 && p == T) { //如果是根節(jié)點且根節(jié)點沒key了,就換根節(jié)點
T = newNode;
height--;
}
//再遞歸處理這個加入的節(jié)點 刪掉mid
//這個加入的節(jié)點n==2t-1,符合條件
return nodeDelete(newNode, newNode.getValueIndex(mid));
} else {
//把孩子節(jié)點的值換過來,再對孩子節(jié)點刪掉該值
int neari = p.c[i].n > p.c[i+1].n? i : i+1;
Node<E, F> other = p.c[neari];
p.kavs[i] = neari == i ? other.kavs[other.n-1] : other.kavs[0];
//再遞歸處理other 刪掉 p.kavs[i]
//因為other.n>t-1不一定成立
return seekAndDeleteNode(p.kavs[i].key, p, p.c[neari], neari);
}
}
}
//當(dāng)p節(jié)點.n為t-1時調(diào)用此程序
private Node<E, F> nodeDeleteHandle(Node<E, F> father, Node<E, F> p, int j) {
if (p == T) {
return p;
}
int nearj = (j == father.n ? j-1:j+1);//假設(shè)父節(jié)點n>t-1
if (father.c[nearj].n > t-1) {//從相鄰結(jié)點借一個關(guān)鍵字過來
KeyAndValue<E, F> kav1 = null;
KeyAndValue<E, F> kav2 = null;
Node<E, F> child = null;
if (nearj == j+1) {
if (!p.isLeaf) {
child = father.c[nearj].deleteChild(0);
}
kav2 = father.kavs[j];
kav1 = father.c[nearj].delete(0);
p.kavs[p.n] = kav2;
p.n++;
father.kavs[j] = kav1;
if (!p.isLeaf) {
p.addChild(child, p.n);
}
} else { //nearj==j-1
if (!p.isLeaf) {
child = father.c[nearj].deleteChild(father.c[nearj].n);
}
kav1 = father.c[nearj].delete(father.c[nearj].n-1);
kav2 = father.kavs[j-1];
p.add(kav2);
father.kavs[j-1] = kav1;
if (!p.isLeaf) {
p.addChild(child, 0);
}
}
return p;
} else {
//把這個結(jié)點與其相鄰結(jié)點合并,合并時需要把父結(jié)點的一個關(guān)鍵字加進(jìn)來
Node<E, F> other = father.c[nearj];
Node<E, F> newNode = null;
father.deleteChild(p);
father.deleteChild(other);
if (nearj == j+1) {
KeyAndValue<E, F> mid = father.delete(j);
newNode = nodeUnion(p, other, mid);
father.addChild(newNode, j);
} else { //nearj==j-1
KeyAndValue<E, F> mid = father.delete(j-1);
newNode = nodeUnion(other, p, mid);
father.addChild(newNode, j-1);
}
//父節(jié)點為空了
if (father.n == 0 && father == T) {
T = newNode;
height--;
}
return newNode;
}
}
private Node<E, F> nodeUnion(Node<E, F> node1,
Node<E, F> node2, KeyAndValue<E, F> mid) { //沒有考慮非葉子節(jié)點的問題
node1.kavs[node1.n] = mid;
node1.n++;
for (int i = 0; i < node2.n; i++) {
node1.kavs[i+node1.n] = node2.kavs[i];
if (!node1.isLeaf) {
node1.c[i+node1.n] = node2.c[i];
}
}
node1.n += node2.n;
if (!node1.isLeaf) {
node1.c[node1.n] = node2.c[node2.n];
}
return node1;
}
class Node<E extends Comparable<E>, F>{
int n = 0;
int tier = 0;
KeyAndValue<E, F>[] kavs = new KeyAndValue[2*t-1];
boolean isLeaf;
Node<E, F>[] c = null;
@Override
public String toString() {
StringBuffer s = new StringBuffer();
s.append(this.tier + " ");
for (int i = 0; i < n; i++) {
s.append(kavs[i] + " ");
}
s.append("\n");
if (!isLeaf) {
for (int j = 0; j <= n; j++) {
s.append(c[j]);
}
}
return s.toString();
}
Node (boolean isLeaf) {
if(!isLeaf) {
c = new Node[2*t];
}
this.isLeaf = isLeaf;
}
E getKey(int i) {
return kavs[i].key;
}
F getValue(int i) {
return kavs[i].value;
}
//使用前提:存在kav
int getValueIndex(KeyAndValue<E, F> kav) {
int i = 0;
while (i < n && kav.key.compareTo(getKey(i)) > 0) { //可改進(jìn)為折半查找
i++;
}
return i;
}
void copyKeyAndValues(Node<E, F> n, int low, int high) {
for (int i = low; i <= high; i++) {
kavs[i-low] = n.kavs[i];
}
this.n=1+high-low;
}
void copyChildren(Node<E, F> n, int low, int high) {
if (isLeaf) {
return;
}
for (int i = low; i <= high; i++) {
c[i-low] = n.c[i];
}
}
void addChild(Node<E, F> node, int i) {
if (isLeaf) {
return;
}
int j = n;
while(j > i) {
c[j] = c[j-1];
j--;
}
c[i] = node;
}
Node<E, F> deleteChild(int i) {
if (isLeaf) {
return null;
}
Node<E, F> child = c[i];
for (int j = i; j < n; j++) {
c[j] = c[j+1];
}
return child;
}
void deleteChild(Node<E, F> node) {
if (isLeaf) {
return;
}
boolean hasFind = false;
for (int i = 0; i < n; i++) {
if (c[i] == node) {
hasFind = true;
}
if (hasFind) {
c[i] = c[i+1];
}
}
}
KeyAndValue<E, F> delete(int i) {
KeyAndValue<E, F> kav = kavs[i];
for (int j = i; j < n-1; j++) {
kavs[j] = kavs[j+1];
}
n--;
return kav;
}
int add(E e, F f) {
return add(new KeyAndValue<E, F>(e, f));
}
int add(KeyAndValue kav) {
int i = n;
//System.out.println("n = " + n);
while (i != 0 && getKey(i-1).compareTo((E) kav.key) > 0) {
kavs[i] = kavs[i-1];
i--;
}
kavs[i] = kav;
n++;
return i;
}
void changeToNonLeaf() {
if (isLeaf) {
isLeaf = false;
c = new Node[2*t];
}
}
}
class KeyAndValue <E extends Comparable<E>, F> {
E key = null;
F value = null;
@Override
public String toString() {
return "[" + key + "," + value + "]";
}
KeyAndValue(E key, F value) {
this.key = key;
this.value = value;
}
}
}
測試代碼:
public static void main(String[] args) {
BTree<Integer, String> t = new BTree<Integer, String>(3);
int n = 16;
for (int i = n-1; i >= 0; i--) {
t.insert(i, i+"");
}
System.out.println(t);
t.delete(n-1);
System.out.println(t);
t.update(1, "3");
System.out.println(t);
}
插入
在B樹中插入一個關(guān)鍵字,只用考慮節(jié)點的關(guān)鍵字?jǐn)?shù)目是否為2*t-1偷线。
- 關(guān)鍵字?jǐn)?shù)目不為2*t-1
直接插入關(guān)鍵字就可以磨确。 - 關(guān)鍵字?jǐn)?shù)目為2t-1
需要分裂節(jié)點,將節(jié)點的中間關(guān)鍵字提上去給父節(jié)點声邦,其他的關(guān)鍵字分成兩個節(jié)點乏奥,各為t-1個關(guān)鍵字。再插入關(guān)鍵字翔忽。但是這時就需要考慮父節(jié)點關(guān)鍵字?jǐn)?shù)目是否為2t-1英融。所以在遍歷b樹的時候發(fā)現(xiàn)節(jié)點關(guān)鍵字?jǐn)?shù)目為2*t-1的時候就要對它進(jìn)行分裂,再向下遍歷歇式。
刪除操作比插入操作考慮的情況更多驶悟,更復(fù)雜,可以參考
http://blog.csdn.net/swordmanwk/article/details/6549480
更詳細(xì)內(nèi)容可以參考《算法導(dǎo)論》