B樹的java實現
1. B樹基本概念
關於B樹的基本概念、定義可以參考這篇部落格:http://blog.csdn.net/kalikrick/article/details/27980007,本文側重於B樹的java程式碼實現。1.1 B樹性質
B樹T有如下性質: 1. 每個節點x有如下屬性:- x.n表示節點當前key的個數。
- x中key滿足:x.key1 <= x.key2<= x.key3 <= .... <= x.keyx,n。也就是x中的key以非降序順序排列。
- x要麼是葉子節點,要麼是內部節點。
- 除根節點外,每個節點必須有至少t-1個key,t個孩子。樹不為空時,根節點至少有一個key。
- 每個節點至多有2*t-1個key,每個內部節點至多有2*t個孩子。當一個節點有2*t-1個key時,稱其為滿節點。
1.2 B樹內部結點的定義
B樹的型別和節點定義如下圖所示:
2. B樹基本操作
2.1 查詢
圖2 -B樹查詢偽碼(來自《演算法導論》)
2.2 插入key
圖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.3 刪除key
刪除示例:圖5 -刪除操作示例(來自《演算法導論》)
3. 程式碼實現
3.1 B_Tree
package com.oracle.ThreetheenthCharpter;
import
import java.util.List;
import java.util.Objects;
public class B_Tree<K extends Comparable<K>,V> {
public BT_Node<K,V> root;
public int degree; //度
public int number; //樹種結點的數量
public B_Tree(int degree) {
if (degree < 2) {
throw new IllegalArgumentException("degree must >= 2");
}
root = null;
number = 0;
this.degree = degree;
}
/**
* B樹的結點
*/
@SuppressWarnings({"rawtypes","unchecked"})
class BT_Node<K extends Comparable<K> ,V>{
BT_Node<K,V> parent;
/*
*注:這裡的children和pointers應該用定義成陣列的(因為連結串列的增加和刪除操作比陣列方便,所以引用連結串列),
*但是實際上,只有B+樹的內部結點才定義了連結串列
*/
final List<Entry<K,V>> children; //存放的鍵值對連結串列
final List<BT_Node<K,V>> pointers; //子樹指標的連結串列
/**
*除根節點外,每個節點必須有至少t-1個key,t個孩子。樹不為空時,根節點至少有一個key。
每個節點至多有2*t-1個key,每個內部節點至多有2*t個孩子。當一個節點有2*t-1個key時,稱其為滿節點。
*/
public BT_Node() {
parent = null;
children = new LinkedList<Entry<K,V>>();
pointers = new LinkedList<BT_Node<K,V>>();
}
/**
*返回鍵值對的數量
*/
public int getKeyNum() {
int keyNum = children.size();
return keyNum;
}
/**
*判斷當前結點是否已滿
*/
public boolean isFull() {
return this.getKeyNum()==2*degree-1;
}
/**
*判斷當前結點是否是葉子結點
*/
public boolean isLeafNode() {
return this.pointers.size()==0;
}
/**
*判斷當前結點的鍵值對數目是否符合約束條件
*/
public boolean isQualified() {
int keyNum = this.getKeyNum();
if(this!=root) {
if(keyNum<(degree-1)) {
throw new IllegalArgumentException("The least keyNum is (degree-1)");
}
if(keyNum >(2*degree-1)) {
throw new IllegalArgumentException("The most keyNum is (2*degree-1)");
}
}
return true;
}
@Override
public String toString() {
return"BT_Node [children=" + children + "]"+" count:"+this.getKeyNum();
}
}
/**
* 返回是否為空
*/
public boolean isEmpty() {
return size()==0;
}
/**
* 返回容量大小
*/
public int size() {
return number;
}/**
* 匿名內部類——鍵值對
*/
private static class Entry<K extends Comparable<K>,V>{
K k;
V v;
public Entry(K k, V v) {
this.k= k;
this.v= v;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Entry) {
Entry<?,?> e = (Entry<?,?>)o;
if (Objects.equals(k, e.k) &&
Objects.equals(v, e.v))
return true;
}
return false;
}
public K getK() {
return k;
}
public V getV() {
return v;
}
@Override
public String toString() {
return"Entry [k=" + k + ",v=" + v + "]";
}
}
/**
* 查詢方法
* B樹的查詢和二叉樹查詢類似,首先在當前節點中查詢,如果沒有並且存在孩子節點,
* 就遞迴的到可能存在該key的孩子節點中查詢。
* 不同的是,B樹節點有多個key,需要每個都比較,為了提高效能,可以使用二分法加速節點中的查詢。
*/
public Entry<K,V> search(BT_Node<K,V> x, K key){
int i =0;
Entry<K,V> entry,next;
while(i<x.getKeyNum()) {
entry = x.children.get(i);
next = (i==x.getKeyNum()-1)?null:x.children.get(i+1);
//遍歷陣列,如果陣列內的元素包含了目標元素,return
if(key.equals(entry.getK())) {
return entry;
}
if(key.compareTo(x.children.get(x.getKeyNum()-1).getK())>0) {
i=x.getKeyNum();
break;
}else {
//如果沒有包括,則需要遞迴尋找
if(key.compareTo(entry.getK())<0) {
i=0;
break;
}else if(key.compareTo(entry.getK())>0 && key.compareTo(next.getK())<0) {
i=i+1;
break;
}
}
i++;
}
//i對應的是子樹指向的連結串列的索引位置,用一個“尾遞迴”
if(x.isLeafNode()) {
return null;
}else {
return search(x.pointers.get(i),key);
}
}
/**
* 1、先判斷當前節點的Entry是否已滿
2、如果已滿,就分裂
3、如果結點是葉子結點做插入操作,不是則向下遞迴
*/
private void insertAction(BT_Node<K,V> current, Entry<K,V> entry) {
//先判斷root結點是否為空
if(root==null) {
root = new BT_Node<K,V>();
current = root;
}
//如果當前結點已滿,需要分裂
if(current.isFull()) {
//分裂
current = split(current);
}
//如果當前結點是葉子結點
if(current.isLeafNode()) {
//插入
int index = getPosition(current, entry);
//System.out.println(index+","+current.getClass());
current.children.add(index, entry);
number++;
return ;
}else{
//否則,向下遞迴尋找
int index = getPosition(current, entry);
//System.out.println("向下遞迴的索引"+index);
insertAction(current.pointers.get(index),entry);
}
}
public void insert(K key, V value) {
Entry<K,V>entry = new Entry(key,value);
insertAction(root,entry);
}
/**
* 該方法用於定位在連結串列中,新增Entry結點適合插入的位置
*/
private int getPosition(BT_Node<K,V> x, Entry<K,V> entry) {
List<Entry<K,V>> list = x.children;
int index =0;
Entry<K,V> ent,next;
for(int i=0;i<list.size();i++) {
ent = x.children.get(i);
next = (i==x.getKeyNum()-1)?null:x.children.get(i+1);
if(entry.k.compareTo(list.get(list.size()-1).k)>0) {
index=list.size();
break;
}else {
//在左邊
if(entry.k.compareTo(ent.k)<=0) {
index=0;
break;
}
//在中間
else if(entry.k.compareTo(next.k)<=0) {
index =i+1;
break;
}
}
}
return index;
}
/**
* 分裂
* B樹的插入需要考慮的一個問題就是當節點以滿時,需要將該節點分裂成兩個節點。
一個滿的節點有2*t-1個key,內部節點有2*t 個孩子,分裂將其分成兩個各有t-1個key,
內部節點各t個孩子,多餘的一個節點插入到父節點中,作為分裂之後兩個節點的分割key。
*/
/*
* 步驟:1、將當前結點的中間Entry結點插入到父節點中,並將剩餘的B_TNode拆分
* 難點:繫結父結點和子結點的關係;
* 定義pointer指標
*/
private BT_Node<K,V> split(BT_Node<K,V> x) {
BT_Node<K,V> parent;
BT_Node<K,V> left = new BT_Node<K,V>();
BT_Node<K,V> right = new BT_Node<K,V>();
int len = x.getKeyNum();
Entry<K,V> mid = x.children.get(len/2);
for(int i=0;i<len;i++) {
if(i<len/2) {
left.children.add(x.children.get(i));
}
if(i>len/2) {
right.children.add(x.children.get(i));
}
}
if(x.isLeafNode()) {
//如果parent為null,說明對根結點做split操作
if(x==root) {
parent = new BT_Node<K,V>();
parent.children.add(mid);
parent.pointers.add(left);
parent.pointers.add(right);
left.parent=parent;
right.parent=parent;
root = parent;
x=root;
}else{
parent = x.parent;
int position = this.getPosition(parent, mid);
parent.children.add(position,mid);
parent.pointers.remove(position);
parent.pointers.add(position, left);
parent.pointers.add(position+1,right);
left.parent=parent;
right.parent=parent;
x=parent;
}
}else {
if(x==root) {
//繫結父節點
parent = new BT_Node<K,V>();
parent.children.add(mid);
parent.pointers.add(left);
parent.pointers.add(rig