本文將主要講述另外一種樹形結構,B 樹;B 樹是一種多路平衡查找樹,可是能夠將其理解爲是由二叉查找樹合併而來;它主要用於在不一樣存儲介質之間查找數據的時候,減小 I/O 次數(由於一次讀一個節點,能夠讀取多個數據);java
B 樹,多路平衡查找樹,即有多個分支的查找樹;如圖所示:node
B 樹主要應用於多級存儲介質之間的查找,圖中的藍色節點爲外部節點,表明下一級存儲介質;綠色節點則爲內部節點;同時咱們將B 樹按照其最大分支樹進行分類,好比圖中的則爲4 階B 樹;緩存
對於 m 階 B 樹(m >= 2):this
public class BTree<E extends Comparable<? super E>> implements Iterable<E> { private Node<E> root; private final int order; private final int MAX_KEYS; private final int MIN_KEYS; private int height; private int totalSize; final class Node<T> { Object[] values; Node<T>[] children; Node<T> parent; boolean isLeaf; int size; Node() { this.values = new Object[order]; // 實際只有order-1個關鍵碼,超出時會分裂; this.children = new Node[order + 1]; // 一樣+1; this.isLeaf = true; this.size = 0; } } }
由於B 樹節點的在[ ⌈m/2⌉ - 1, m -1 ]
之間,因此在動態插入和刪除的過程當中必定會發生不平衡,下面將介紹修復不平衡的幾種方法;指針
插入時當節點的關鍵碼超過 m-1
,就將大節點分爲兩個小節點;如圖:code
分裂時:blog
⌊m/2⌋
個關鍵碼移入父節點;實現:遞歸
private void split(Node<E> p) { Node<E> parent = p.parent; if (parent == null) { // parent爲null,即當前節點爲root,須要上升高度(惟一會致使樹高度增長的操做) parent = new Node<E>(); parent.isLeaf = false; // 設置爲非葉子節點 root = parent; // 更新root節點 height++; // 高度加1 } int mid = (p.size - 1) >>> 1; // 須要上一的關鍵碼 Node<E> left = new Node<E>(); // 分裂,建立一個新的空節點 Node<E> right = p; // 右邊節點爲原來的節點 left.isLeaf = p.isLeaf; // 節點是否葉子,取決於分裂前是否葉子。 // 更新孩子節點的parent指針 if (!p.isLeaf) { for (int i = 0; i <= mid; ++i) { // 左子樹的孩子應該指向左子樹。 p.children[i].parent = left; } } parent.insertToNonLeaf((E) p.values[mid], left, right); // 把中間節點插入父節點。 int i, j; // 拷貝右子樹信息到左子樹。 for (i = 0; i < mid; ++i) { left.values[i] = right.values[i]; left.children[i] = right.children[i]; } left.children[i] = right.children[mid]; left.size = mid; // 更新左子樹關鍵字數量 // 刪除右子樹多餘關鍵字和孩子,由於已經拷貝到左孩子中去了。 for (i = mid + 1, j = 0; i < right.size; ++i, ++j) { right.values[j] = right.values[i]; right.children[j] = right.children[i]; } right.children[j] = right.children[right.size]; // 更新最後一個孩子節點, 注意奇數j == mid,但偶數不是。。 right.size = right.size - mid - 1; // 更新右子樹關鍵字數量 left.parent = parent; // 把子樹的父親節點更新 right.parent = parent; if (parent.size > MAX_KEYS) // 若是父親節點也達到最大關鍵字數量,須要遞歸分裂。 split(parent); } int insertToNonLeaf(T key, Node<T> left, Node<T> right) { int index = insertIndex(key); if (index < 0) return index; for (int i = size; i > index; --i) { values[i] = values[i - 1]; children[i + 1] = children[i]; } children[index] = left; children[index + 1] = right; values[index] = key; size++; return index; }
刪除節點時,可能會致使節點的關鍵碼數量小於 ⌊m/2⌋
,此時能夠向他的左孩子或者右孩子,借一個關鍵碼;如圖:ci
圖中:rem
右孩子富裕時左旋:
private void leftRotate(Node<E> p) { Node<E> right = rightSibling(p); // 獲取右兄弟 int myRank = rankInChildren(p); // 獲取在父節點中的秩 Object oldSeparator = p.parent.values[myRank]; p.values[p.size] = oldSeparator; p.size++; Object newSeparator = right.values[0]; Node<E> child = right.isLeaf ? null : right.children[0]; // 獲取右兄弟中最小的關鍵碼 int i; for (i = 0; i < right.size - 1; ++i) { right.values[i] = right.values[i + 1]; if (!right.isLeaf) right.children[i] = right.children[i + 1]; } if (!right.isLeaf) { right.children[right.size - 1] = right.children[right.size]; child.parent = p; p.children[p.size] = child; } right.size--; p.parent.values[myRank] = newSeparator; } private Node<E> rightSibling(Node<E> p) { if (p == null || p.parent == null) // 根節點無兄弟節點 return null; Node<E> parent = p.parent; int i = rankInChildren(p); if (i >= 0 && i < parent.size) { return parent.children[i + 1]; } return null; }
左孩子富裕時右旋:
private void rightRotate(Node<E> p) { Node<E> left = leftSibling(p); int myRank = rankInChildren(p); Object oldSeparator = p.parent.values[myRank - 1]; Node<E> child = null; if (!left.isLeaf) { child = left.children[left.size]; p.children[p.size + 1] = p.children[p.size]; } for (int i = p.size; i >= 1; --i) { p.values[i] = p.values[i - 1]; if (!p.isLeaf) p.children[i] = p.children[i - 1]; } if (!left.isLeaf) { child.parent = p; p.children[0] = child; } p.values[0] = oldSeparator; p.size++; Object newSeparator = left.values[left.size - 1]; left.size--; p.parent.values[myRank - 1] = newSeparator; } private Node<E> leftSibling(Node<E> p) { if (p == null || p.parent == null) return null; Node<E> parent = p.parent; int i = rankInChildren(p); if (i >= 1) return parent.children[i - 1]; return null; }
當左右孩子的關鍵碼都不足以借出時,則將兩個孩子合併,如圖:
圖中:
實現:
private void merge(Node<E> p) { Node<E> parent = p.parent; assert (parent != null); Node<E> left = p; // left node 或者是當前節點,即貧困節點,或者是當前節點的左兄弟節點。 Node<E> right = rightSibling(p); if (right == null) { left = leftSibling(p); right = p; } int myRank = rankInChildren(left); // 把父親節點的Separator下移到須要合併的節點left Object separator = parent.values[myRank]; left.values[left.size] = separator; left.size++; // 從父親節點中刪除Separator for (int i = myRank; i < parent.size - 1; i++) { parent.values[i] = parent.values[i + 1]; parent.children[i + 1] = parent.children[i + 2]; } //FIXME parent.values[parent.size - 1] = null; parent.children[parent.size] = null; parent.size--; // 拷貝右節點到左節點 for (int i = 0; i < right.size; ++i) { left.size++; left.values[left.size - 1] = right.values[i]; if (!left.isLeaf) { right.children[i].parent = left; // donot forget it. left.children[left.size - 1] = right.children[i]; } } // 不要忘記最後一個孩子更新。 if (!left.isLeaf) { right.children[right.size].parent = left; left.children[left.size] = right.children[right.size]; } // 若是父親節點也貧困了,須要從父親節點從新調整,直到知足平衡或者父親節點就是root節點 if (parent.size < MIN_KEYS) { if (parent.size == 0 && parent == root) { root = left; root.parent = null; height--; } else { rebalancingAfterDeletion(parent); } } }
查找時採起逐層查找:
實現:
public Node<E> search(E e) { Node<E> v = root; while (v != null) { // 逐層查找 int r = v.search(e); // 在當前節點中,找到不大於e的最大關鍵碼 if (r >= 0 && cmp(e, v.values[r]) == 0) { return v; } v = v.children[r + 1]; // 轉入對應子樹——需作I/O,最費時間 } return null; } int search(T key) { int low = 0; int high = size - 1; do { int mi = (low + high) >> 1; if (cmp(key, values[mi]) < 0) { high = mi; } else { low = mi + 1; } } while (low < high); return --low; }
public boolean add(E key) { if (key == null) { return false; } if (root == null) { root = new Node<E>(); this.height = 1; this.totalSize = 0; } boolean inserted = insert(key, root); if (inserted) { ++totalSize; ++modCount; return true; } else { return false; } } private boolean insert(E key, Node<E> p) { assert (p != null); if (!p.isLeaf) { // 老是插入到葉子中,不可能直接插入到內部節點 int index = p.insertIndex(key); // 獲取插入位置,若是 < 0說明已存在 if (index < 0) // index < 0 說明key已存在 return false; return insert(key, p.children[index]); // 插入的位置就是孩子的位置 } boolean inserted = p.insertToLeaf(key) >= 0; // p是葉子節點,直接插入。 if (p.size > MAX_KEYS) { // 若是關鍵字多於最大關鍵字數量,須要分裂節點。 split(p); } return inserted; } int insertToLeaf(T key) { int index = insertIndex(key); if (index < 0) return index; for (int i = size; i > index; --i) {// 移動向右key values[i] = values[i - 1]; } values[index] = key; ++size; return index; }
public boolean remove(E e) { if (root == null) { return false; } boolean isRemoved = remove(e, root); if (isRemoved) { --totalSize; ++modCount; } return isRemoved; } private boolean remove(E e, Node<E> p) { if (p.isLeaf) { // 刪除的關鍵字在葉子節點中,直接刪除,而後從新調整 boolean isRemoved = p.deleteFromLeaf(e); if (p.size < MIN_KEYS) { rebalancingAfterDeletion(p); // rebalances the tree } return isRemoved; } int index = p.binarySearch(e); if (index < 0) { // 不在吃節點中,遞歸從子樹中查找。 return remove(e, p.children[-index - 1]); // -index - 1就是插入位置,即孩子節點位置。 } // 刪除的是內部節點,須要尋找左子樹最大節點(或者右子樹中最小節點)做爲新分隔符替換刪除的關鍵字。 Node<E> leftLeaf = leftLeaf(p, index);// 尋找左子樹最右節點。 Object candidate = leftLeaf.values[leftLeaf.size - 1]; //從葉子節點中移除候選節點 leftLeaf.values[leftLeaf.size - 1] = null; leftLeaf.size--; //候選節點做爲分隔符替代刪除的節點。 p.values[index] = candidate; //從新調整樹使其平衡。 if (leftLeaf.size < MIN_KEYS) { rebalancingAfterDeletion(leftLeaf); } return true; } boolean deleteFromLeaf(T key) { int index = binarySearch(key); if (index < 0) return false; for (int i = index; i < size; ++i) { values[i] = values[i + 1]; } this.size--; return true; } private void rebalancingAfterDeletion(Node<E> p) { if (p == root) { // 說明p是root節點,不須要處理 return; } Node<E> left = leftSibling(p); // 獲取左兄弟 if (left != null && left.size > MIN_KEYS) { // 左兄弟很富裕, 右旋轉。 rightRotate(p); return; } Node<E> right = rightSibling(p); // 右兄弟 if (right != null && right.size > MIN_KEYS) { // 若是右兄弟節點富裕,左旋轉。 leftRotate(p); return; } merge(p); }