爲學習 LLVM 的 ImmutableSet,其底層的實現選擇爲 AVL 樹(平衡二叉搜索樹),我不很熟悉該樹,雖然大體知道但畢竟不精,所以仍是先學習學習二叉搜索樹吧。java
二叉搜索樹或叫作二叉查找樹,能夠用於實現Dictionary辭典,辭典是K,V 鍵值的對的集合。通常 Dictionary 不含重複的 K。一個 Dictionary 的抽象數據類型規範可定義爲:node
interface Dictionary<K extends Comparable<K>, V> { // 當且僅當辭典爲空的時候返回真。 public boolean isEmpty(); // 若是存在鍵值爲 k 的 <K,V> 對,則返回它們。 public Entry<K, V> get(K k); // 插入給出的 k,v 對,若是鍵值已經存在則更新相關元素 v。 public void insert(K k, V v); // 刪除特定鍵值的 k,v 對。 public void remove(K k); }
二叉查找樹的定義有4條,參見書便可。咱們最需關注的是在二叉查找樹中位於左子樹的鍵小於根的鍵,右子樹的鍵大於根的鍵。下面具體學習如何實現 isEmpty, get, insert, remove 等方法。小程序
假設節點與樹的結構以下(方法實如今 BSTree 類中):學習
// 表示一個樹節點。 class TreeNode<K, V> { TreeNode left; // 左子樹 TreeNode right; // 右子樹 K key; // 鍵 V value; // 元素值 } // 表示一個二叉查找樹。 class BSTree<K, V> implements Dictionary { TreeNode<K, V> root; // 根節點。 }
方法 isEmpty 比較簡單,判斷 root 是否爲空便可。不用多探討了。this
方法 get 以下:3d
// 在 BSTree 類中. public TreeNode get(K k) { if (this.root == null) return null; // 沒有任何節點 return this.root.find(k); } // 在 TreeNode 類中。 public TreeNode find(K k) { TreeNode<K, V> node = this; while (node != null) { int c = k.compareTo(node.getKey()); if (c < 0) node = node.getLeft(); // 查找左子樹。 else if (c > 0) node = node.getRight(); // 查找右子樹。 else return node; // 找到了。(c == 0) } return null; // 沒有找到。 }
在這裏使用了二叉查找樹的核心性質,根據 compare 結果,若是 k 小於當前節點的鍵則查找左子樹,大於則查找右子樹,若是相等則就是本身。也能夠使用遞歸方法寫查找,可是效率上可能不如這樣寫好。code
insert 方法實現以下:遞歸
public void insert(K k, V v) { if (k == null) throw new NullPointerException("k"); TreeNode<K, V> new_node = new TreeNode<K, V>(k, v); if (this.root == null) { // 特殊狀況,沒有任何節點的時候。 this.root = new_node; return; } // 在樹中搜索 k 的節點 p, pp 表示 p 的父節點。 TreeNode<K, V> p = this.root, pp = null; while (p != null) { pp = p; // 若是 p 走向了子節點,則 pp 剛好是 p 的父節點。 int c = k.compareTo(p.getKey()); if (c < 0) // 走向左節點。 p = p.getLeft(); else if (c > 0) // 走向右節點。 p = p.getRight(); else { // p 的 key 就是 k,找到同鍵值的,則更新 v 返回。 p.setValue(v); return; } } // 若是走到這裏,則 p == null, pp 是前一個節點,新的節點應插入到 pp 下面(左、右取決於鍵值的比較) if (k.compareTo(pp.getKey()) < 0) // k < pp.key 表示應插入到 pp 的左子樹 pp.left = new_node; else pp.right = new_node; }
remove 實現以下:rem
private TreeNode<K, V> internal_remove(K k) { if (this.root == null) return null; // 沒有任何節點的特殊狀況。 TreeNode<K, V> p = this.root, pp = null; // 查找要刪除的鍵值爲 k 的節點 p,以及其父節點 pp。 // 若是 pp 爲 null 表示 p 是根節點 root。 while (p != null) { int c = k.compareTo(p.getKey()); if (c == 0) break; pp = p; p = (c < 0) ? p.left : p.right; } if (p == null) return null; // 沒有找到要刪除的元素。 // 如今 p 是要刪除的節點,pp 是其父節點(可能爲 null)。 if (p.left == null || p.right == null) { // 這裏 p 至多隻有一個子樹,則就用這一個子樹替代 p 便可。 set_pp_child(pp, p, p.left != null ? p.left : p.right); } else { // p 有兩個子樹。能夠用 left.max or right.min 來替代 p。咱們選用右邊的最左子樹(min)。 TreeNode<K, V> pR = p.right; if (pR.left == null) { // 右邊的子樹沒有左分支,則用 p_right 替代 p 便可。 pR.left = p.left; set_pp_child(pp, p, pR); return p; } // p 的右子樹 pR 有左子樹,則從中刪除最左的節點 -- rmin。 TreeNode<K, V> rmin = internal_remove_rmin(pR); rmin.left = p.left; // 用 rmin 替代 p,所以設置 rmin.left, .right rmin.right = pR; set_pp_child(pp, p, rmin); } return p; } // 移除指定節點的最小子節點(pR.min),也即最左子節點。 private TreeNode<K, V> internal_remove_rmin(TreeNode<K, V> pR) { if (pR.left == null) throw new java.lang.RuntimeException("pR.left is null"); TreeNode<K, V> rmin = pR.left, rmin_pp = pR; // rmin_pp 是 rMin 的父節點。 while (rmin.left != null) { rmin_pp = rmin; rmin = rmin.left; // 向左走到最左子節點,其就是 pR.min。 } // 如今 rmin = pR.min, rmin_pp = rmin.parent。 assert (rmin.left == null); // 如今 rmin 必然沒有左子樹了。 rmin_pp.left = rmin.right; rmin.right = null; return rmin; } private void set_pp_child(TreeNode<K, V> pp, TreeNode<K, V> p, TreeNode<K, V> child) { if (pp == null) this.root = child; // 刪除的就是根,設置根爲 child else if (pp.left == p) pp.left = child; // p 是 pp 的左子樹,替代爲 child else pp.right = child; // p 是 pp 的右子樹,替代爲 child }
remove 方法的實現顯得笨拙一些。get
爲了方便研究 binary search tree,我編寫了一個 java 小程序,位於這裏:http://vdisk.weibo.com/s/3dT-U/1331863473
該程序能夠經過輸入命令 find, insert, delete, dump 等構造和查看二叉查找樹。幫助爲 help, 退出爲 exit。
下一步準備在二叉查找樹的基礎上實現/學習 AVL 樹。