AVL樹-scala實現

二叉查找樹已經可以很好的應用到應用程序中,但它們在最壞的狀況下性能仍是很糟糕。考慮到如圖所示這種狀況:AVL2 查找操做的性能徹底等同於線性。而AVL樹的查找操做,可以保證不管怎麼構造它,運行時間一直對數級別的。一塊兒來學習一下AVL樹吧。java

什麼是AVL

AVL(Adelson-Velsky 和 Landis)樹,是帶有平衡條件的二叉查找樹,這個平衡條件必需要容易保持,而且它保證樹的深度是O(LogN).算法

一顆AVL樹是其每一個節點的左右子樹節點高度最多差1的二叉查找樹。編程

AVL1

平衡條件

我門上面說,一顆AVL樹是其每一個節點的左右子樹節點高度最多差1的二叉查找樹。 左右子樹的高度最多差1即是AVL樹的平衡條件。數據結構

當進行插入操做時,咱們須要更新通往根節點的全部節點的平衡信息。而插入操做隱含困難的緣由在於,可能會破壞AVL樹的特性(例如,考慮將6插入到圖1的AVL樹中將會破壞節點爲8的平衡條件)。若是發生這種情形,那麼就要考慮在插入完成以後從新回覆節點的平衡信息。事實上,總能夠經過對樹進行簡單的修正獲得,咱們稱其爲旋轉(ratation).app

在插入之後,只有那些從插入點到根節點的平衡條件可能變化,由於只有這些節點的字數可能發生變化。當咱們沿着這條路路徑行進到根節點,能夠發現一個節點它的新平衡破壞了AVL樹的特性。咱們須要在這個節點從新平衡這棵樹。ide

咱們把必須從新平衡的節點叫作α。由於平衡條件是高度最多相差1,那麼出現不平衡就是α點的高度相差爲2。容易看出這種不平衡可能出如今下面這四種狀況下:性能

  1. 對α的左兒子的左子樹進行一次插入
  2. 對α的左兒子的右子樹進行一次插入
  3. 對α的右兒子的左子樹進行一次插入
  4. 對α的右兒子的右子樹進行一次插入

1和4,2和3 是關於α點的鏡像對稱。所以理論上講只有兩種狀況,固然編程上仍是四種狀況。學習

第一種狀況是插入發生在外邊的狀況(即左-左的狀況或者右-右的狀況),該狀況是經過對樹的一次單旋轉來完成操做。this

第二種狀況是插入發生在內部的情形(即左-右的狀況或者右-左的狀況),該狀況經過稍微複雜點的雙旋轉操做來處理。scala

單旋轉

下圖顯示了單旋轉如何調整情形1。旋轉前的圖在左邊,旋轉後的圖在右邊。 AVL3

讓咱們來分析具體的作法。節點K2不知足平衡條件, 由於它的左子樹比右子樹深兩層(圖中的虛線表示樹的各層).

爲使樹恢復平衡,咱們把節點X向上移一層,並把Z像下移一層,爲此咱們從新安排節點以造成一顆等價的樹。如上圖的右邊部分。抽象的形容就是,把把樹想象成一棵柔軟靈活的樹,抓住子節點K1,閉上眼睛使勁的搖動它,這樣因爲重力的做用,K1就變成了新的根。二叉樹的性質告訴咱們,在原樹中K2>K1,這樣在新樹中,K2變成了K1的右兒子,Y變成了K2的左子樹,而X、Z依然是K1的左子樹和K2的右子樹。

AVL4

讓咱們演示一個更長的例子,假設從初始的空的AVL樹開始依次插入三、二、1,而後再依次插入4-7.

在插入關鍵字1的時候第一個問題就出現了,AVL性質在根處被破壞,咱們在根和其左兒子之間施加單旋轉操做來修正問題。下圖是旋轉以前和以後的兩棵樹。

AVL5

圖中虛線鏈接的兩個節點,它們是旋轉的主體。下面咱們插入關鍵字4這沒有問題,在咱們插入關鍵字5的時候,問題又出現了,平衡條件在關鍵字3處被破壞,而經過單旋轉再次將問題修復。

AVL6

接下來插入6,平衡條件在根節點處被打破,所以咱們在根處,在二、4之間施加一次單旋轉操做。

AVL7

咱們插入最後的關鍵字7,它致使另外的旋轉。

AVL8

雙旋轉

單旋轉對情形二、3無效願因在於子樹Y太深。單旋轉沒有下降它的深度。

AVL9

對於子樹Y咱們能夠假設他有一個根和兩棵子樹(對應上圖的K二、B、C)。

爲了平衡期間,咱們不能再把K3當成根了,而如上圖所示K3和K1之間旋轉又解決不了問題,惟一的選擇是把K2當成根,這迫K1成爲K2的左子樹,K3成爲K1的右子樹,而K2的左子樹成爲K1的右子樹,K2的右子樹成爲K3的左子樹。容易看出最後獲得的樹知足AVL樹的特徵。

咱們繼續在前面例子的基礎上以倒序插入10-16,接着插入8,再插入9.插入16很容易這並不破壞樹的平衡,插入15會引發節點7處的高度不平衡,這屬於情形3,須要經過右-左雙旋轉來解決。如圖:

AVL10

下面咱們來插入14,它也須要一個雙旋轉。此時修復樹的旋轉仍是右-左雙旋轉

add14

若是如今插入13那麼在根處就會出現不平衡,因爲13不在4-7之間,咱們知道只要一次單旋轉就行.

add13

插入12也須要一次雙旋轉

add12

插入十一、10都須要進行單旋轉,接着咱們插入8不須要進行旋轉,這樣就建成了一顆近乎理想的平衡樹

add8

最後咱們插入9,在節點10發生不平衡,這裏知足情形2,對左兒子的右子樹進行一次,須要一次雙旋轉。

after

總結

如今讓咱們對上面的討論作出總結,除幾種情形外,編程的細節是至關簡單的。爲將節點X插入AVL樹T中,咱們遞歸的將X插入到T的子樹中。若是子樹的高度不變,那麼插入完成;若是出現高度不平衡,那麼找到不平衡點,作適當的單旋轉或者雙旋轉,更新這些高度,從而完成插入。

scala實現

sealed trait AVLTree[+A] extends Serializable {
  def balance: Int

  def depth: Int

  def contains[B >: A](x: B)(implicit ordering: Ordering[B]): Boolean

  def insert[B >: A](x: B)(implicit ordering: Ordering[B]): AVLTree[B]


  def rebalance[B >: A]: AVLTree[B]

}

/**
 * 葉子節點
 */
case object Leaf extends AVLTree[Nothing] {

  override val balance: Int = -1
  override val depth: Int = 0

  override def contains[B >: Nothing](x: B)(implicit ordering: Ordering[B]): Boolean = false



  override def insert[B >: Nothing](x: B)(implicit ordering: Ordering[B]): AVLTree[B] = {
    Node(x, Leaf, Leaf)
  }


  override def rebalance[B >: Nothing]: AVLTree[B] = this

}

case class Node[A](value: A, left: AVLTree[A], right: AVLTree[A]) extends AVLTree[A] {
  override def balance: Int = right.depth - left.depth

  override def depth: Int = math.max(left.depth, right.depth)

  override def contains[B >: A](x: B)(implicit ordering: Ordering[B]): Boolean = ???

  override def insert[B >: A](x: B)(implicit ordering: Ordering[B]): AVLTree[B] = {
    val cmp = ordering.compare(x,value)
    if (cmp < 0){
      Node(value, left.insert(x),right).rebalance
    } else if (cmp > 0){
      Node(value, left, right.insert(x)).rebalance
    } else {
      this
    }
  }

  override def rebalance[B >: A]  = {
    if (-2 == balance){
      if (1 == left.balance){
        doubleRightRotation
      } else {
        rightRotation
      }
    } else if(2 == balance) {
      if (-1 == right.balance){
        doubleLeftRotation
      }else {
        leftRotation
      }
    } else {
      this
    }
  }


  /*
   *   5
   *    \                                  6
   *     6       ---- 左單旋轉 --->        /  \
   *      \                              5   7
   *       7
   *
   *   在節點5 發生不平衡 5的右節點成爲新的根節點
   */
  private def leftRotation[B >: A] = {
    if (Leaf != right) {
      val r = right.asInstanceOf[Node[A]]
      Node(r.value, Node(value, left, r.left), r.right)
    } else {
      sys.error("should not happen.")
    }
  }


  /*
   *           7
   *          /                                6
   *         6    ---- 右單旋轉 --->           /  \
   *        /                                5   7
   *       5
   *
   *    在節點7 發生不平衡 7的左節點成爲新的根節點
   */
  private def rightRotation[B >: A] = {
    if (Leaf != left) {
      val l = left.asInstanceOf[Node[A]]
      Node(l.value, l.left, Node(value, l.right, right))
    } else {
      sys.error("should not happen.")
    }

  }

  /*
   *     左雙旋轉
   *
   *     5                           5
   *       \                          \                     6
   *        7    ----step1 --->        6    ---step2--->   / \
   *       /                            \                 5   7
   *      6                              7
   *
   *  在節點5處不平衡
   *  step1 : 節點5的右節點 作一次 右旋轉
   *  step2 : 左旋轉
   *
   */
  private def doubleLeftRotation[B >: A] = {
    if (Leaf != right) {
      val r = right.asInstanceOf[Node[A]]
      val rightRotated = r.rightRotation
      Node(rightRotated.value, Node(value, left, rightRotated.left), rightRotated.right)
    } else {
      sys.error("should not happen.")
    }
  }

  /*
  *   右雙旋轉
  *
  *     7                            7
  *    /                            /                   6
  *   5       ----step1 --->       6    ---step2--->   / \
  *    \                          /                   5   7
  *     6                        5
  *
  *  在節點7處不平衡
  *  step1 : 節點7的左節點 作一次 左旋轉
  *  step2 : 右旋轉
  *
  */
  private def doubleRightRotation[B >: A] = {
    if (Leaf != left) {
      val l: Node[A] = left.asInstanceOf[Node[A]]
      val leftRotated = l.leftRotation
      Node(leftRotated.value, leftRotated.left, Node(value, leftRotated.right, right))
    } else sys.error("Should not happen.")
  }

}

參考資料

《數據結構與算法分析java語言描述》 AVL tree (維基百科)

相關文章
相關標籤/搜索