B-Tree(Balance Tree)的Java實現
阿新 • • 發佈:2018-12-25
我就直接上自己的Java程式碼,之前寫的時候也參考了一些其它語言的實現 。
我的實現裡主要加入了泛型,提供了put(key, value); get(key); 兩個操作。 大體上看就是一個HashMap的功能。/** * <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; } } }
刪除節點那部分的程式碼我這裡沒貼,因為實在太複雜也太長,我的程式碼也未必能讓大家看的明白, 如果有興趣的,自己動手實現一遍更好。