演算法#16--B樹完整程式碼Java實現
定義
在電腦科學中,B樹(英語:B-tree)是一種自平衡的樹,能夠保持資料有序。這種資料結構能夠讓查詢資料、順序訪問、插入資料及刪除的動作,都在對數時間內完成。
為什麼要引入B樹?
首先,包括前面我們介紹的紅黑樹是將輸入存入記憶體的一種內部查詢樹。
而B樹是前面平衡樹演算法的擴充套件,它支援儲存在磁碟或者網路上的符號表進行外部查詢,這些檔案可能比我們以前考慮的輸入要大的多(難以存入記憶體)。
既然內容儲存在磁碟中,那麼自然會因為樹的深度過大而造成磁碟I/O讀寫過於頻繁(磁碟讀寫速率是有限制的),進而導致查詢效率低下。
那麼降低樹的深度自然很重要了。因此,我們引入了B樹,多路查詢樹。
特點
樹中每個結點最多含有m個孩子(m>=2);
除根結點和葉子結點外,其它每個結點至少有[ceil(m / 2)]個孩子(其中ceil(x)是一個取上限的函式);
若根結點不是葉子結點,則至少有2個孩子(特殊情況:沒有孩子的根結點,即根結點為葉子結點,整棵樹只有一個根節點);
所有葉子結點都出現在同一層(最底層),葉子結點為外部結點,儲存內容,即key和value。
其他結點為內部結點,儲存索引,即key和next。
內部結點的關鍵字key:K[1], K[2], …, K[M-1];且K[i] < K[i+1];
內容結點的指標next:P[1], P[2], …, P[M];其中P[1]指向關鍵字小於K[1]的子樹,P[M]指向關鍵字大於K[M-1]的子樹,其它P[i]指向關鍵字屬於(K[i-1], K[i])的子樹;
例如:(M=3)
查詢和插入
為了方便這裡用了一個特殊的哨兵鍵,它小於其他所有鍵,用*表示。
一開始B樹只含有一個根結點,而根結點在初始化時僅含有該哨兵鍵。
內部結點中的每個鍵都與一個結點相關聯,以此結點為根的子樹種,所有的鍵都大於等於與此結點關聯的鍵,但小於其他所有鍵。
這些約定在很大程度上能夠簡化程式碼。
程式碼
點選下載。
該程式碼實現引入了哨兵鍵,程式碼輸出則剔除了它。
程式碼裡含有哨兵鍵的B樹(將圖片儲存到本地檢視,字會清晰些):
程式碼輸出的B樹(將圖片儲存到本地檢視,字會清晰些):
public class BTree <Key extends Comparable<Key>, Value>
{
// max children per B-tree node = M-1
// (must be even and greater than 2)
private static final int M = 4;
private Node root; // root of the B-tree
private int height; // height of the B-tree
private int n; // number of key-value pairs in the B-tree
// helper B-tree node data type
private static final class Node
{
private int m; // number of children
private Entry[] children = new Entry[M]; // the array of children
// create a node with k children
private Node(int k)
{
m = k;
}
}
// internal nodes: only use key and next
// external nodes: only use key and value
private static class Entry
{
private Comparable key;
private Object val;
private Node next; // helper field to iterate over array entries
public Entry(Comparable key, Object val, Node next)
{
this.key = key;
this.val = val;
this.next = next;
}
}
/**
* Initializes an empty B-tree.
*/
public BTree()
{
root = new Node(0);
}
/**
* Returns true if this symbol table is empty.
* @return {@code true} if this symbol table is empty; {@code false} otherwise
*/
public boolean isEmpty()
{
return size() == 0;
}
/**
* Returns the number of key-value pairs in this symbol table.
* @return the number of key-value pairs in this symbol table
*/
public int size()
{
return n;
}
/**
* Returns the height of this B-tree (for debugging).
*
* @return the height of this B-tree
*/
public int height()
{
return height;
}
/**
* Returns the value associated with the given key.
*
* @param key the key
* @return the value associated with the given key if the key is in the symbol table
* and {@code null} if the key is not in the symbol table
* @throws NullPointerException if {@code key} is {@code null}
*/
public Value get(Key key)
{
if (key == null)
{
throw new NullPointerException("key must not be null");
}
return search(root, key, height);
}
@SuppressWarnings("unchecked")
private Value search(Node x, Key key, int ht)
{
Entry[] children = x.children;
// external node到最底層葉子結點,遍歷
if (ht == 0)
{
for (int j = 0; j < x.m; j++)
{
if (eq(key, children[j].key))
{
return (Value) children[j].val;
}
}
}
// internal node遞迴查詢next地址
else
{
for (int j = 0; j < x.m; j++)
{
if (j+1 == x.m || less(key, children[j+1].key))
{
return search(children[j].next, key, ht-1);
}
}
}
return null;
}
/**
* Inserts the key-value pair into the symbol table, overwriting the old value
* with the new value if the key is already in the symbol table.
* If the value is {@code null}, this effectively deletes the key from the symbol table.
*
* @param key the key
* @param val the value
* @throws NullPointerException if {@code key} is {@code null}
*/
public void put(Key key, Value val)
{
if (key == null)
{
throw new NullPointerException("key must not be null");
}
Node u = insert(root, key, val, height); //分裂後生成的右結點
n++;
if (u == null)
{
return;
}
// need to split root重組root
Node t = new Node(2);
t.children[0] = new Entry(root.children[0].key, null, root);
t.children[1] = new Entry(u.children[0].key, null, u);
root = t;
height++;
}
private Node insert(Node h, Key key, Value val, int ht)
{
int j;
Entry t = new Entry(key, val, null);
// external node外部結點,也是葉子結點,在樹的最底層,存的是內容value
if (ht == 0)
{
for (j = 0; j < h.m; j++)
{
if (less(key, h.children[j].key))
{
break;
}
}
}
// internal node內部結點,存的是next地址
else
{
for (j = 0; j < h.m; j++)
{
if ((j+1 == h.m) || less(key, h.children[j+1].key))
{
Node u = insert(h.children[j++].next, key, val, ht-1);
if (u == null)
{
return null;
}
t.key = u.children[0].key;
t.next = u;
break;
}
}
}
for (int i = h.m; i > j; i--)
{
h.children[i] = h.children[i-1];
}
h.children[j] = t;
h.m++;
if (h.m < M)
{
return null;
}
else
{ //分裂結點
return split(h);
}
}
// split node in half
private Node split(Node h)
{
Node t = new Node(M/2);
h.m = M/2;
for (int j = 0; j < M/2; j++)
{
t.children[j] = h.children[M/2+j];
}
return t;
}
/**
* Returns a string representation of this B-tree (for debugging).
*
* @return a string representation of this B-tree.
*/
public String toString()
{
return toString(root, height, "") + "\n";
}
private String toString(Node h, int ht, String indent)
{
StringBuilder s = new StringBuilder();
Entry[] children = h.children;
if (ht == 0)
{
for (int j = 0; j < h.m; j++)
{
s.append(indent + children[j].key + " " + children[j].val + "\n");
}
}
else
{
for (int j = 0; j < h.m; j++)
{
if (j > 0)
{
s.append(indent + "(" + children[j].key + ")\n");
}
s.append(toString(children[j].next, ht-1, indent + " "));
}
}
return s.toString();
}
// comparison functions - make Comparable instead of Key to avoid casts
private boolean less(Comparable k1, Comparable k2)
{
return k1.compareTo(k2) < 0;
}
private boolean eq(Comparable k1, Comparable k2)
{
return k1.compareTo(k2) == 0;
}
/**
* Unit tests the {@code BTree} data type.
*
* @param args the command-line arguments
*/
public static void main(String[] args)
{
BTree<String, String> st = new BTree<String, String>();
st.put("www.cs.princeton.edu", "128.112.136.12");
st.put("www.cs.princeton.edu", "128.112.136.11");
st.put("www.princeton.edu", "128.112.128.15");
st.put("www.yale.edu", "130.132.143.21");
st.put("www.simpsons.com", "209.052.165.60");
st.put("www.apple.com", "17.112.152.32");
st.put("www.amazon.com", "207.171.182.16");
st.put("www.ebay.com", "66.135.192.87");
st.put("www.cnn.com", "64.236.16.20");
st.put("www.google.com", "216.239.41.99");
st.put("www.nytimes.com", "199.239.136.200");
st.put("www.microsoft.com", "207.126.99.140");
st.put("www.dell.com", "143.166.224.230");
st.put("www.slashdot.org", "66.35.250.151");
st.put("www.espn.com", "199.181.135.201");
st.put("www.weather.com", "63.111.66.11");
st.put("www.yahoo.com", "216.109.118.65");
System.out.println("cs.princeton.edu: " + st.get("www.cs.princeton.edu"));
System.out.println("hardvardsucks.com: " + st.get("www.harvardsucks.com"));
System.out.println("simpsons.com: " + st.get("www.simpsons.com"));
System.out.println("apple.com: " + st.get("www.apple.com"));
System.out.println("ebay.com: " + st.get("www.ebay.com"));
System.out.println("dell.com: " + st.get("www.dell.com"));
System.out.println();
System.out.println("size: " + st.size());
System.out.println("height: " + st.height());
System.out.println(st);
System.out.println();
}
}
輸出:
cs.princeton.edu: 128.112.136.12
hardvardsucks.com: null
simpsons.com: 209.052.165.60
apple.com: 17.112.152.32
ebay.com: 66.135.192.87
dell.com: 143.166.224.230
size: 17
height: 2
www.amazon.com 207.171.182.16
www.apple.com 17.112.152.32
www.cnn.com 64.236.16.20
(www.cs.princeton.edu)
www.cs.princeton.edu 128.112.136.12
www.cs.princeton.edu 128.112.136.11
www.dell.com 143.166.224.230
(www.ebay.com)
www.ebay.com 66.135.192.87
www.espn.com 199.181.135.201
www.google.com 216.239.41.99
(www.microsoft.com)
www.microsoft.com 207.126.99.140
www.nytimes.com 199.239.136.200
(www.princeton.edu)
www.princeton.edu 128.112.128.15
www.simpsons.com 209.052.165.60
(www.slashdot.org)
www.slashdot.org 66.35.250.151
www.weather.com 63.111.66.11
(www.yahoo.com)
www.yahoo.com 216.109.118.65
www.yale.edu 130.132.143.21