【算法】論平衡二叉樹(AVL)的正確種植方法

參考資料
《算法(java)》                           — — Robert Sedgewick, Kevin Wayne
《數據結構》                                  — — 嚴蔚敏
 
 
2017年度原創IT博客評選:http://www.itbang.me/goVote/203
 
引子
 
近日, 爲了響應市政府「全市綠化」的號召, 身爲共青團員的我決定在家裏的後院挖坑種二叉樹,以支援政府實現節能減排的偉大目標,並進一步爲實現共同富裕和民族復興打下堅實的基礎....
 
咳咳, 很差意思,扯遠了。 額, 就是我上次不是種二叉查找樹嘛(見上面的連接),發現大多數二叉樹都長的比較好,但總有那麼那麼幾顆長勢很不如人意,我對此感到很疑惑(你們思考一下這是爲何)
 
直到——  看門的李大爺給我送過來了一包樹種,神祕兮兮地跟我說這是能自動吸取氮磷鉀,猶如金坷垃般神奇的樹種, 它叫    ——   「平衡二叉樹」
 
正文開始
 

平衡二叉樹的由來

普通二叉搜索樹的缺陷

 
普通二叉搜索樹的動態方法多是「有缺陷」的, 或者說: 可能會帶來不良的反作用
 
普通二叉搜索樹的API分爲兩種: 靜態方法動態方法
靜態方法不會對二叉樹作修改,而僅僅是獲取相關的信息,例如:
 
get(根據key獲取val)
max(獲取最大key),
min(獲取最小key)
floor(對key向下取整)
ceiling(對key向上取整)
rank(獲取給定key的排名)
select(根據排名得到給定key)

 

 
而動態方法則會修改樹中結點, 並進一步影響二叉樹的結構
put (插入鍵值對)
delete(刪除鍵值對)

BST的動態方法可能會修改二叉樹的結構, 使其結點分佈不均勻,使得在下一步的操做中, 靜態方法和動態方法都變得更爲低效。html

 

插入的順序影響二叉搜索樹的構造

 
一樣的數據集合, 插入二叉搜素樹中的順序的不一樣,樹的形狀和結構也是不一樣的
 
以put方法爲例,咱們重複調用它, 用key爲1, 2, 3, 4的結點構造一顆二叉搜索樹。那麼這顆二叉搜索樹的形狀取決於不一樣的key的插入順序
 
可能在你眼裏,構造的樹多是比較「均勻」的。但讓咱們看看, 若是按照徹底正序或者逆序輸入, 二叉搜索樹的形狀就會走向一個很差的極端:
 
若是按照 1 -> 2 -> 3 -> 4 的順序插入, 那麼這顆二叉樹在形狀上會變得像一顆單鏈表!
 

 

一樣,若是按照4 -> 3 -> 2 ->1 的順序插入, 它在形狀上會變成一顆向左傾斜的鏈表
 

 

 

爲何二叉搜索樹會變得低效?

二叉搜索樹查找的原理和二分查找相似,就是藉助於它自己的結構,在遍歷查找的過程當中跳過一些沒必要要的結點的比較,從而實現高效的查找。 BST的其餘API也是藉助了這一優點實現性能的飛躍。可是,在這種狀況下, 查找一個結點將要像鏈表同樣遍歷它通過的全部結點, 二叉搜索樹的高效之源已經喪失了。 這就是最壞的狀況。
 

插入和刪除操做均可能下降將來操做的性能

上面我只講述了插入操做對二叉樹形狀和操做性能的影響, 但讓咱們反向思考一下就會發現,刪除操做的效果也有相似之處: 可能使得原來分佈得比較均勻的結點, 在刪除部分結點以後,總體的分佈變得不均勻了,並影響到將來操做的性能。
 
這裏我先先入爲主地灌輸一個關於「平衡」的概念: 「二叉搜索樹各結點分佈均勻、各類操做都較爲高效的狀態」
 

什麼是平衡二叉樹

綜上所述,咱們但願在進行動態操做(插入和刪除)以後,可以經過一些指標,對二叉樹的形狀變化進行監督, 當發現樹的形狀開始變得不平衡的時候, 當即修正二叉樹的形狀。
 
經過這種方式, 不斷地使得二叉樹的形狀和構造維持着一個「平衡」的狀態, 添加了這種維護機制的二叉搜索樹, 就是平衡二叉樹
 
上個圖,對比一下普通的二叉搜索樹和平衡二叉樹的區別:
 
普通的二叉搜索樹(BST)
 

 

 
平衡二叉樹(AVL)
 

 

 
這還不夠? 再來個動圖看一看!
(圖中key的大小關係:按字母排序,A最小,Z最大)

 

 
這裏咱們能夠很明顯地看到平衡二叉樹的優點所在: 使得查找的平均深度下降, 優化各個API的性能開銷
 

AVL和普通BST區別在於動態方法java

平衡二叉樹和普通二叉查找樹區別主要在於動態方法!(put,delete) 。它們的靜態方法基本是相同的! (get,min,max,floor,ceiling, rank,select)
 
因此本文編寫的主要API就只有兩個: put和delete
 

平衡二叉樹的監督機制

咱們前面提到了平衡二叉樹有它的監督機制,既然說到「監督」, 那必然就有一個用於判斷當前二叉樹平不平衡的指標, 這個監督的指標, 就是平衡因子(Balance Factor)。
 
在二叉樹中, 咱們爲每一個結點定義了平衡因子這個屬性。
 
平衡因子: 某個結點的左子樹的高度減去右子樹的高度獲得的差值。
平衡二叉樹(AVL): 全部結點的平衡因子的絕對值都不超過1。即對平衡二叉樹每一個結點來講,其左子樹的高度 - 右子樹高度獲得的差值只能爲 1, 0 , -1 這三個值。 取得小於 -1或者大於1的值,都被視爲打破了二叉樹的平衡
 
圖解平衡因子
下圖中:
對根結點A而言, 它左子樹高度爲2, 右子樹高度爲1, 那麼它的平衡因子BF = 2 - 1 = 1
對結點B而言, 它左子樹高度爲1, 沒有右子樹(高度視爲0),BF = 1 - 0 = 1;
 

 

圖解平衡二叉樹
以下圖所示, 圖a的兩顆是平衡二叉樹, 圖b的兩顆則是非平衡二叉樹
 

 

 
因此, 只有全部結點都符合「平衡因子的絕對值都不超過1」 這一條件的二叉樹, 纔是平衡二叉樹;
若是有一個結點不符合條件, 那麼這顆二叉樹就不是平衡二叉樹。
 
上面咱們說到, 在動態操做(插入/刪除)的過程當中,咱們須要平衡因子做爲「指標」, 去監督當前這顆二叉樹的構造是否符合預期, 即——是不是一顆平衡二叉樹。
 
而平衡因子BF的計算須要用到該節點的孩子結點的高度屬性, 這也就意味着, 咱們要從Node類的實例變量入手,爲每一個結點設置height屬性, 並在二叉樹結構發生變化時, 更新並維護height的正確性。
 

爲每一個結點設置並維護height屬性

 
height屬性的設置
啊, 終於能夠開始寫代碼了。 以下,咱們在Node類中寫入了實例變量height,並初始化爲1
/**
 * @Author: HuWan Peng
 * @Date Created in 10:35 2017/12/29
 */
public class AVL {
  Node root; // 根結點
  private class Node {
    int key,val;
    Node left,right;
    int height = 1; // 每一個結點的高度屬性
    public Node (int key, int val) {
      this.key = key;
      this.val = val;
    }
  }
  // 編寫API方法
}

 

height屬性的維護和更新
讓咱們思考一下, 結點height屬性在何時會發生變化: 固然是在二叉樹結構發生變化的時候, 具體表現爲:
  1. 在插入結點時(put), 沿插入的路徑更新結點的高度值(不必定會加1 !只是要從新計算)
  2. 在刪除結點時(delete),沿刪除的路徑更新結點的高度值(不必定減1! 只是要從新計算)
  3. 在發現二叉樹變得不平衡的時候, 經過「旋轉」使其平衡, 這時候要更新相關結點的高度值(具體的我下面會詳細講)
 
下面的代碼是更新結點高度的示範例子:
 
  /**
   * @description: 返回兩個數中的最大值
   */
  private int max (int a, int b) {
    return a>b ? a : b;
  }
 
  /**
   * @description: 得到當前結點的高度
   */
  private int height (Node x) {
    if(x == null) return 0;
    return x.height;
  }
 
  // 下面的insert方法是簡化後的代碼
  public Node insert (Node x, int key, int val) {
    其餘代碼 。。。。
    insert(x.left, key, val); // 進行遞歸的插入
    x.height = max(height(x.left),height(x.right)) + 1; // 更新結點的height屬性(沿着遞歸路徑)
    return x;
  }

 

 
最關鍵的是
  x.height = max(height(x.left),height(x.right)) + 1;

 

這一句代碼, 由於在遞歸的插入或刪除以後,沿着遞歸路徑上方的結點的height都有可能會改變, 因此要經過依次調用這一段代碼, 沿着遞歸路徑自下而上地更新沿途結點的height屬性值。
 

計算BF以監督平衡二叉樹的狀態

只要咱們能正確地維護每一個結點的height, 咱們就能對動態操做中受影響的結點,準確計算其平衡因子(BF), 從而判斷當前的平衡二叉樹的狀態
 
計算某個結點平衡因子的方法:
  /**
   * @description: 得到平衡因子
   */
  private int getBalance (Node x) {
    if(x == null) return 0;
    return height(x.left) - height(x.right);
  }

 

 

平衡二叉樹的修正機制

當咱們計算出某個結點的平衡因子的絕對值超過1時, 咱們就要對其進行修正, 即經過平衡化的處理,使得不平衡的二叉樹從新變得平衡。
 

左旋和右旋

二叉樹的平衡化有兩大基礎操做: 左旋和右旋
1. 左旋,便是逆時針旋轉;右旋, 便是順時針旋轉
2. 這種旋轉在整個平衡化過程當中可能進行一次或屢次
3.且是從失去平衡的最小子樹根結點開始的(即離插入結點最近的、平衡因子超過1的祖先結點)
 
右旋操做
右旋操做過程:使結點3位置「下沉」,而結點2位置「上浮」, 反轉當前結點和它左兒子的父子關係。
 

 

 
可是, 讓咱們思考地再全面一些: 若是上圖中的結點2有右兒子的話, 狀況會變得怎樣?
這時候結點2將保持有3條連接, 若是在這種狀況下旋轉, 結點二須要拋棄一條連接。
 
咱們的處理方式是: 拋棄結點2的右兒子, 將其和旋轉後的結點3鏈接,成爲結點3的左兒子
 
我將上面的這種假設的結點戲稱爲「拖油瓶」結點,  以下圖中的黃色結點
 

 

 
緊接上圖, 咱們須要先斷開4結點和3結點間的連接, 而後把它轉接到旋轉後的結點5上:
 

 

 
固然, 有的時候咱們假設的這個「拖油瓶」結點(黃色結點)多是空的,可是這並不影響咱們的編碼。
 
好嘞! 讓咱們來編寫右旋的代碼:
  /**
   * @description: 右旋方法
   */
  private Node rotateRight (Node x) {
    Node y = x.left; // 取得x的左兒子
    x.left = y.right; // 將x左兒子的右兒子("拖油瓶"結點)連接到旋轉後的x的左連接中
    y.right = x; // 調轉x和它左兒子的父子關係,使x成爲它原左兒子的右子樹
    x.height = max(height(x.left),height(x.right)) + 1; // 更新並維護受影響結點的height
    y.height = max(height(y.left),height(y.right)) + 1; // 更新並維護受影響結點的height
    return y; // 將y返回
  }

 

 
 
左旋操做
左旋操做的過程和右旋同樣:
 
例以下面:
 
1. 結點2位置「下沉」, 而結點4位置上浮,反轉當前結點和它右兒子的父子關係(2和4), 使2結點變成4結點的左兒子。
2. 同時斷裂結點3和結點4間的連接, 轉接到結點2中(處理拖油瓶結點)
 

 

 
左旋方法代碼以下:
  /**
   * @description: 左旋方法
   */
  private  Node rotateLeft (Node x) {
    Node y = x.right;  // 取得x的右兒子
    x.right = y.left; // 將x右兒子的左兒子("拖油瓶"結點)連接到旋轉後的x的右連接中
    y.left = x; // 調轉x和它右兒子的父子關係,使x成爲它原右兒子的左子樹
    x.height = max(height(x.left),height(x.right)) + 1; // 更新並維護受影響結點的height
    y.height = max(height(y.left),height(y.right)) + 1; // 更新並維護受影響結點的height
    return y; // 將y返回
  }

 

 

平衡化操做的四種狀況

以左旋操做和右旋操做爲基礎, 構成了平衡化操做的四種狀況
 
假設因爲在二叉排序樹上插入結點而失去平衡的最小子樹的根結點爲a (即a是離插入結點最近的、平衡因子超過1的祖先結點) 則失去平衡後的調整操做分爲如下4種狀況:
 
1. 單次右旋: 因爲在a的左子樹的根結點的左子樹上插入結點(LL),使a的平衡因子由1變成2, 致使以a爲根的子樹失去平衡, 則需進行一次的向右的順時針旋轉操做
 

 

 
2. 單次左旋: 因爲在a的右子樹根結點的右子樹上插入結點(RR),a的平衡因子由-1變成-2,致使以a爲根結點的子樹失去平衡,則須要進行一次向左的逆時針旋轉操做

 

 
3. 兩次旋轉、先左旋後右旋: 因爲在a的左子樹根結點的右子樹上插入結點(LR), 致使a的平衡因子由1變成2,致使以a爲根結點的子樹失去平衡,須要進行兩次旋轉, 先左旋後右旋
 

 

 
 
4.兩次旋轉, 先右旋後左旋: 因爲在a的右子樹根結點的左子樹上插入結點(RL), a的平衡因子由-1變成-2,致使以a爲根結點的子樹失去平衡, 則須要進行兩次旋轉,先右旋後左旋
 

 

 
那麼問題來了,怎麼分別判斷LL, RR,LR,RL這四種破環平衡的場景呢?
咱們能夠根據當前破壞平衡的結點的平衡因子, 以及其孩子結點的平衡因子來判斷,具體以下圖所示:
 

 

 
(BF表示平衡因子, 最下方的那個結點是新插入的結點)
 

編寫平衡化代碼

有了以上的知識基礎, 讓咱們來編寫下咱們的平衡化代碼
 
  /**
   * @description: 得到平衡因子
   */
  private int getBalance (Node x) {
    if(x == null) return 0;
    return height(x.left) - height(x.right);
  }
  /**
   * @description: 平衡化操做:  檢測當前結點是否失衡,若失衡則進行平衡化
   */
  private Node reBalance (Node x) {
    int balanceFactor = getBalance(x);
    if(balanceFactor > 1&&getBalance(x.left)>0) { // LL型,進行單次右旋
     return rotateRight(x);
    }
    if(balanceFactor > 1&&getBalance(x.left)<=0) { //LR型 先左旋再右旋
      Node t = rotateLeft(x);
      return rotateRight(t);
    }
    if(balanceFactor < -1&&getBalance(x.right)<=0) {//RR型, 進行單次左旋
      return rotateLeft(x);
    }
    if(balanceFactor < -1&&getBalance(x.right)>0) {// RL型,先右旋再左旋
      Node t = rotateRight(x);
      return rotateLeft(t);
    }
    return x;
  }

 

 AVL類的API編碼

下面我將展現平衡二叉樹的put方法和delete方法的代碼, 而這兩個方法絕大部分的代碼仍是基於二叉查找樹的put方法和delete方法的, 因此還不太瞭解BST的同窗能夠看一看我上篇文章對BSTput方法和delete方法的解析:
 

插入方法

在看代碼前能夠先看下對二叉查找樹中put方法的解析
 
平衡查找樹的put方法
  /**
   * @description: 插入結點(鍵值對)
   */
  public Node put (Node x, int key, int val) {
    if(x == null) return new Node(key, val); // 插入鍵值對
    if     (key<x.key) x.left  = put(x.left, key, val); // 向左子樹遞歸插入
    else if(key>x.key) x.right = put(x.right,key, val); // 向右子樹遞歸插入
    else x.val = val; // key已存在, 替換val
    x.height = max(height(x.left),height(x.right)) + 1; // 沿遞歸路徑從下至上更新結點height屬性
    x = reBalance(x); // 沿遞歸路徑從下往上, 檢測當前結點是否失衡,若失衡則進行平衡化
    return x;
  }

 

 

刪除方法

刪除方法比較複雜,在看代碼前能夠先看下對二叉查找樹中put方法的解析
 
平衡查找樹的delete方法
  /**
   * @description: 返回最小鍵
   */
  private Node min (Node x) {
    if(x.left == null) return x; // 若是左兒子爲空,則當前結點鍵爲最小值,返回
    return min(x.left);  // 若是左兒子不爲空,則繼續向左遞歸
  }
  public int min () {
    if(root == null) return -1;
    return min(root).key;
  }
 
  /**
   * @description: 刪除最小鍵的結點
   */
  public Node deleteMin (Node x) {
    if(x.left==null) return x.right; // 若是當前結點左兒子空,則將右兒子返回給上一層遞歸的x.left
    x.left = deleteMin(x.left);// 向左子樹遞歸, 同時重置搜索路徑上每一個父結點指向左兒子的連接
    return x; // 當前結點不是min
  }
  public void deleteMin () {
    root = deleteMin(root);
  }
 
  /**
   * @description: 刪除給定key的鍵值對
   */
  private Node delete (int key,Node x) {
    if(x == null) return null;
    if      (key<x.key) x.left  = delete(key,x.left); // 向左子樹查找鍵爲key的結點
    else if (key>x.key) x.right = delete(key,x.right); // 向右子樹查找鍵爲key的結點
    else{
      // 結點已經被找到,就是當前的x
      if(x.left==null) return x.right; // 若是左子樹爲空,則將右子樹賦給父節點的連接
      if(x.right==null) return x.left; // 若是右子樹爲空,則將左子樹賦給父節點的連接
      Node inherit = min(x.right); // 取得結點x的繼承結點
      inherit.right = deleteMin(x.right); // 將繼承結點從原來位置刪除,並重置繼承結點右連接
      inherit.left = x.left; // 重置繼承結點左連接
      x = inherit; // 將x替換爲繼承結點
    }
    if(root == null) return root;
    x.height = max(height(x.left),height(x.right)) + 1; // 沿遞歸路徑從下至上更新結點height屬性
    x = reBalance(x); // 沿遞歸路徑從下往上, 檢測當前結點是否失衡,若失衡則進行平衡化
    return x;
  }
  public void delete (int key) {
    root = delete(key, root);
  }

 

 

測試AVL和BST的動態操做對二叉樹結構的影響

下面咱們用層序遍歷的方式進行測試:
  /**
   * @description: 二叉樹層序遍歷
   */
  private void levelIterator () {
    LinkedList <Node> queue = new LinkedList <Node>();
    Node current = null;
    int childSize = 0;
    int parentSize = 1;
    queue.offer(root);
    while(!queue.isEmpty()) {
      current = queue.poll();//出隊隊頭元素並訪問
      System.out.print(current.val +" ");
      if(current.left != null)//若是當前節點的左節點不爲空入隊
      {
        queue.offer(current.left);
        childSize++;
      }
      if(current.right != null)//若是當前節點的右節點不爲空,把右節點入隊
      {
        queue.offer(current.right);
        childSize++;
      }
      parentSize--;
      if (parentSize == 0)
      {
        parentSize = childSize;
        childSize = 0;
        System.out.println("");
      }
    }
  }

 

 
測試普通BST
  public static void main(String [] args) {
    BST bst = new BST();
    bst.put(1,11);
    bst.put(2,22);
    bst.put(3,33);
    bst.put(4,44);
    bst.put(5,55);
    bst.put(6,66);
    bst.levelIterator();
  }

 

輸出:
(6層!!!)
11
22
33
44
55
66

 

測試AVL:
  public static void main (String [] args) {
    AVL avl = new AVL();
    avl.put(1,11);
    avl.put(2,22);
    avl.put(3,33);
    avl.put(4,44);
    avl.put(5,55);
    avl.put(6,66);
    avl.levelIterator();
  }

 

輸出:
(只有3層!)
44
22 55
11 33 66

 

所有代碼

 
 
import java.util.LinkedList;
 
/**
* @Author: HuWan Peng
* @Date Created in 10:35 2017/12/29
*/
public class AVL {
  Node root; // 根結點
  private class Node {
    int key,val;
    Node left,right;
    int height = 1; // 每一個結點的高度屬性
    public Node (int key, int val) {
      this.key = key;
      this.val = val;
    }
  }
  /**
   * @description: 返回兩個數中的最大值
   */
  private int max (int a, int b) {
    return a>b ? a : b;
  }
 
  /**
   * @description: 得到當前結點的高度
   */
  private int height (Node x) {
    if(x == null) return 0;
    return x.height;
  }
  /**
   * @description: 得到平衡因子
   */
  private int getBalance (Node x) {
    if(x == null) return 0;
    return height(x.left) - height(x.right);
  }
 
  /**
   * @description: 右旋方法
   */
  private Node rotateRight (Node x) {
    Node y = x.left; // 取得x的左兒子
    x.left = y.right; // 將x左兒子的右兒子("拖油瓶"結點)連接到旋轉後的x的左連接中
    y.right = x; // 調轉x和它左兒子的父子關係,使x成爲它原左兒子的右子樹
    x.height = max(height(x.left),height(x.right)) + 1; // 更新並維護受影響結點
    y.height = max(height(y.left),height(y.right)) + 1; // 更新並維護受影響結點
    return y; // 將y返回
  }
 
  /**
   * @description: 左旋方法
   */
  private  Node rotateLeft (Node x) {
    Node y = x.right;  // 取得x的右兒子
    x.right = y.left; // 將x右兒子的左兒子("拖油瓶"結點)連接到旋轉後的x的右連接中
    y.left = x; // 調轉x和它右兒子的父子關係,使x成爲它原右兒子的左子樹
    x.height = max(height(x.left),height(x.right)) + 1; // 更新並維護受影響結點
    y.height = max(height(y.left),height(y.right)) + 1; // 更新並維護受影響結點
    return y; // 將y返回
  }
 
  /**
   * @description: 平衡化操做
   */
  private Node reBalance (Node x) {
    int balanceFactor = getBalance(x);
    if(balanceFactor > 1&&getBalance(x.left)>0) { // LL型,進行單次右旋
     return rotateRight(x);
    }
    if(balanceFactor > 1&&getBalance(x.left)<=0) { //LR型 先左旋再右旋
      Node t = rotateLeft(x);
      return rotateRight(t);
    }
    if(balanceFactor < -1&&getBalance(x.right)<=0) {//RR型, 進行單次左旋
      return rotateLeft(x);
    }
    if(balanceFactor < -1&&getBalance(x.right)>0) {// RL型,先右旋再左旋
      Node t = rotateRight(x);
      return rotateLeft(t);
    }
    return x;
  }
  /**
   * @description: 插入結點(鍵值對)
   */
  public Node put (Node x, int key, int val) {
    if(x == null) return new Node(key, val); // 插入鍵值對
    if     (key<x.key) x.left  = put(x.left, key, val); // 向左子樹遞歸插入
    else if(key>x.key) x.right = put(x.right,key, val); // 向右子樹遞歸插入
    else x.val = val; // key已存在, 替換val
    x.height = max(height(x.left),height(x.right)) + 1; // 沿遞歸路徑從下至上更新結點height屬性
    x = reBalance(x); // 沿遞歸路徑從下往上, 檢測當前結點是否失衡,若失衡則進行平衡化
    return x;
  }
 
  public void put (int key,int val) {
    root = put(root,key,val);
  }
 
  /**
   * @description: 返回最小鍵
   */
  private Node min (Node x) {
    if(x.left == null) return x; // 若是左兒子爲空,則當前結點鍵爲最小值,返回
    return min(x.left);  // 若是左兒子不爲空,則繼續向左遞歸
  }
  public int min () {
    if(root == null) return -1;
    return min(root).key;
  }
 
  /**
   * @description: 刪除最小鍵的結點
   */
  public Node deleteMin (Node x) {
    if(x.left==null) return x.right; // 若是當前結點左兒子空,則將右兒子返回給上一層遞歸的x.left
    x.left = deleteMin(x.left);// 向左子樹遞歸, 同時重置搜索路徑上每一個父結點指向左兒子的連接
    return x; // 當前結點不是min
  }
  public void deleteMin () {
    root = deleteMin(root);
  }
 
  /**
   * @description: 刪除給定key的鍵值對
   */
  private Node delete (int key,Node x) {
    if(x == null) return null;
    if      (key<x.key) x.left  = delete(key,x.left); // 向左子樹查找鍵爲key的結點
    else if (key>x.key) x.right = delete(key,x.right); // 向右子樹查找鍵爲key的結點
    else{
      // 結點已經被找到,就是當前的x
      if(x.left==null) return x.right; // 若是左子樹爲空,則將右子樹賦給父節點的連接
      if(x.right==null) return x.left; // 若是右子樹爲空,則將左子樹賦給父節點的連接
      Node inherit = min(x.right); // 取得結點x的繼承結點
      inherit.right = deleteMin(x.right); // 將繼承結點從原來位置刪除,並重置繼承結點右連接
      inherit.left = x.left; // 重置繼承結點左連接
      x = inherit; // 將x替換爲繼承結點
    }
    if(root == null) return root;
    x.height = max(height(x.left),height(x.right)) + 1; // 沿遞歸路徑從下至上更新結點height屬性
    x = reBalance(x); // 沿遞歸路徑從下往上, 檢測當前結點是否失衡,若失衡則進行平衡化
    return x;
  }
  public void delete (int key) {
    root = delete(key, root);
  }
 
  private void levelIterator () {
    LinkedList <Node> queue = new LinkedList <Node>();
    Node current = null;
    int childSize = 0;
    int parentSize = 1;
    queue.offer(root);
    while(!queue.isEmpty()) {
      current = queue.poll();//出隊隊頭元素並訪問
      System.out.print(current.val +"-->");
      if(current.left != null)//若是當前節點的左節點不爲空入隊
      {
        queue.offer(current.left);
        childSize++;
      }
      if(current.right != null)//若是當前節點的右節點不爲空,把右節點入隊
      {
        queue.offer(current.right);
        childSize++;
      }
      parentSize--;
      if (parentSize == 0)
      {
        parentSize = childSize;
        childSize = 0;
        System.out.println("");
      }
    }
  }
 
  public static void main (String [] args) {
    AVL avl = new AVL();
    avl.put(1,11);
    avl.put(2,22);
    avl.put(3,33);
    avl.put(4,44);
    avl.put(5,55);
    avl.put(6,66);
    avl.levelIterator();
  }
}

 

相關文章
相關標籤/搜索