學習數據結構 AVL樹

前一篇學習了二叉搜索樹,本篇試圖學習 AVL 樹。惋惜的是,Mehta 教授的《數據結構基礎》一書中沒有給出
刪除算法,我還沒研究清楚,只好先不寫(學)刪除部分了。java

修改後的 TestTree.java 文件在這裏: http://vdisk.weibo.com/s/3fAzF算法

如下是 insert 部分的代碼,註釋我已經寫得挺清楚的了:數據結構

public void insert(K key, V value) {
    this.checkValid();  // 檢查如今樹的有效性。
    internal_insert(key, value);  // 實際執行插入。
    this.checkValid();  // 插入以後再檢查一次。
  }

  /** 內部實現 insert, 返回插入或更新的節點對象。 */
  private boolean internal_insert(K key, V value) {
    if (this.root == null) {  
      // 特定狀況,簡單處理。
      this.root = new TreeNode<K,V>(key, value);
      return true;
    }
    
    // 第一步:查找 key 的插入位置。
    boolean found = false;
    TreeNode<K, V> a = root,  // 離插入點最近的 bf=+-1 的節點,也可能爲 root(其 bf 可能爲 0)。
      f = null,        // a 的父節點,可能爲 null。
      p = root,        // p 用於根據 key 訪問節點樹。
      q = null,        // q 是 p 的父節點,可能爲 null。
      y = null;        // y 是新插入的節點,或原來已經在樹中的節點。
    while (p != null && !found) {  // p 遍歷從 root -> key 節點的整個路徑。
      if (p.bf != 0) {
        a = p;    // 記錄 a 爲最接近的那個 bf=+-1 的節點。
        f = q;
      }
      int comp = key.compareTo(p.key);
      if (comp < 0) {
        q = p;    // 若是 key < p.key 則查找左子樹,q = p.parent。
        p = p.left;
      }
      else if (comp > 0) {
        q = p;    // 若是 key > p.key 則查找右子樹。
        p = p.right;
      }
      else {      // key == p.key,則 y 已經在樹中了。
        y = p;
        found = true;
      }
    } // end of while: 查找插入位置。
    if (found) {
      y.setValue(value);
      return false;
    }
    
    // 第二步:插入新節點並從新平衡樹。如今 key 不在樹中,並應做爲 pp 的某個
    //   子節點插入到樹中。
    assert(q != null);    // 必定非空,因至少有 root 節點。
    assert(key.compareTo(q.key) != 0);  // key != pp.key
    y = new TreeNode<K, V>(key, value);  // 要插入的新節點。
    if (key.compareTo(q.key) < 0)
      q.left = y;  // key < pp.key,做爲左子樹插入到 pp 中。
    else
      q.right = y;  // key > pp.key,做爲右子樹插入到 pp 中。
    
    // 調整從節點 a->pp 路徑上的節點的平衡因子 bf(balance factor)
    
    int d;    // d = +-1 表示要調整的 bf 的大小。+1 意味着插入到了左子樹,-1 意味着插入到了右子樹。
    TreeNode<K, V> b,  // a 的子節點,多是 Al|Ar。
             c;  // b 的子節點,多是 Bl|Br。
    if (key.compareTo(a.key) > 0) {  // (k > a.key)
      b = p = a.right;  // 插入到了 a 的右邊,則設置 b,p 是 a 的右子樹。
      d = -1;        // d=-1 意味着插入到了右子樹。
    } else {
      b = p = a.left;    // 插入到了 a 的左邊,則設置 b,p 是 a 的左子樹。
      d = +1;        // d=+1 意味着插入到了左子樹。
    }
    
    // 遍歷從 p=a.child -> y 的節點路徑,這些節點的 bf=0,根據插入節點的方向調整 bf 值。
    while (p != y) {
      assert(p.bf == 0);
      if (key.compareTo(p.key) > 0) {
        p.bf = -1;    // 被插入到了右邊,則 bf 右傾斜 -1
        p = p.right;
      } else {
        p.bf = +1;    // 被插入到了左邊,則 bf 左傾斜 +1
        p = p.left;
      }
    } // end of while (p != y);
    
    // a 節點原來 a.bf=+-1,若是更改了則還平衡嗎?
    if (is_balance(a.bf + d)) {    // is_balance 爲 Math.abs(a.bf+d) <= 1
      a.bf += d;
      return true;
    }
    
    // 新的 a.bf = 2 或者 -2
    assert((a.bf + d) == 2 || (a.bf + d) == -2);
    if (d == 1) {      // 插入到左子樹,則 a.bf = 2 (必左傾斜)
      assert(a.bf + d == 2);
      if (b.bf == 1) {  // b 是 a 的子節點,則插入到了 B.left 方向。
        // 執行一次 LL 旋轉。A.right 不變; A.left=原B.right; B.left 不變; B.right=A
        // 旋轉以後,A.bf=0,因 h(Al)==h(Ar);B.bf=0,因h(Bl)=h(Br)=h(A)
        a.left = b.right;
        b.right = a;
        a.bf = 0; b.bf = 0;
      } else {      // 插入到了 B.right 右子樹,則 b.bf = -1
        assert(b.bf == -1);
        // 執行一次 LR 旋轉(至關於 RR+LL 旋轉)。參見書上的 圖10.13。
        c = b.right;
        b.right = c.left;
        a.left = c.right;
        c.left = b; c.right = a;
        // 根據原來的 c.bf 值(可能值是 -1,0,1) 分別計算新的 a,b,c 的 bf 值。
        if (c.bf == 1) {      // h(Cl)=h, h(Cr)=h-1,則 h(new_B)=0, h(new_A)=-1
          b.bf = 0; a.bf = -1;
        } else if (c.bf == -1) {  // h(Cl)=h-1, h(Cr)=h,則 h(new_B)=1, h(new_A)=0
          b.bf = 1; a.bf = 0;
        } else if (c.bf == 0) {    // h(Cl)==h(Cr)==h,則 h(new_B)=0, h(new_A)=0
          b.bf = 0; a.bf = 0;
        }
        c.bf = 0; b = c;  // b 如今指向新的根(替換了原來的 a)
      } // end of if
    } else {
      assert(d == -1); assert(a.bf + d == -2);
      // 插入到了右子樹,則 a.bf = -2(必右傾斜)
      if (b.bf == -1) {  // 插入到了 B 的右子樹,則執行 RR 旋轉。
        a.right = b.left;
        b.left = a;
        a.bf = 0; b.bf = 0;    // 旋轉以後,h(Al)=h(new_ar=old_Bl)=h, h(new_Bl=A)=h(Br)=h+1
      } else {      // 要執行 RL 旋轉。
        assert(b.bf == 1);
        c = b.left;    // 書上沒有圖了,須要本身畫圖。
        a.right = c.left;
        b.left = c.right;
        c.left = a; c.right = b;
        // 根據 c.bf 值(可能爲 -1,0,1),分別計算新的 a,b,c 的 bf 值。
        if (c.bf == 1) {
          a.bf = 0; b.bf = -1;  // bf(A)=h(Al)-h(Cl)=h-h=0; bf(B)=h(Cr)-h(Br)=(h-1)-h=-1
        } else if (c.bf == -1) {
          a.bf = 1; b.bf = 0;    // bf(A)=h(Al)-h(Cl)=h-(h-1)=1; bf(B)=h(Cr)-h(Br)=h-h=0
        } else if (c.bf == 0) {
          a.bf = 0; b.bf = 0;    // bf(A)=h-h=0; bf(B)=h-h=0
        }
        c.bf = 0; b = c;  // 同 LR 中。
      }
    }
    
    // f 是 a 的父節點,若是 a 是 root 則 f == null。經旋轉以後 b(或c) 如今替代了 a 。
    if (f == null) this.root = b;
    else if (a == f.left) f.left = b;
    else if (a == f.right) f.right = b;
    
    return true;
  }

爲方便補上 RL 旋轉部分,畫圖以下:學習

AVL RL 旋轉示意圖

比較費事的地方是根據 bf(C)=-1,0,1 三種狀況分別要計算旋轉後的 bf(A), bf(B) 的值。用圖能夠方便地
標記旋轉後的 CL, CR 的高度(通常爲 h 或 h-1),而後計算出 bf(A), bf(B) 的值。 this

相關文章
相關標籤/搜索