詳細理解平衡二叉樹AVL與Python實現


前言

上一篇文章討論的二叉搜索樹,其時間復雜度最好的情況下是O(log(n)),但是最壞的情況是O(n),什么時候是O(n)呢?

像這樣:

如果先插入10,再插入20,再插入30,再插入40就會成上邊這個樣子

這個就像是雙向鏈表,我們期望它是下面這個樣子:

所以我們希望有一種策略能夠將第一個圖變成第二個圖,或者說使樹的結構不會產生像第一種圖的形式

實現這種策略的一種方式是AVL樹

AVL樹

AVL樹的名稱是以它的發明家的名字命名的:Adel’son-Vel’skii和Landis

滿足高度平衡屬性的二叉樹就是AVL樹

高度平衡屬性是:對於樹中的每一個位置p,p的孩子的高度最多相差1

很顯然前言中的第一個圖並不滿足高度平衡屬性,第二個是滿足的。

同時高度平衡屬性也意味着一顆AVL樹的子樹同樣是AVL樹

並且可以通過證明(這里就不再證了)得到AVL樹的高度是O(log n)

所以得出結論,AVL樹可以使時間復雜度保持O(log n)

接下來的問題就是怎樣保持二叉樹的高度平衡屬性

保持二叉樹的高度平衡屬性

要保持高度平衡屬性的原因是破壞了高度平衡屬性

破壞的方式有兩種:添加節點與刪除節點

添加節點如圖:

添加50的時候,就會破壞高度平衡屬性

刪除節點如圖:

刪除10的時候也會破壞高度平衡屬性

最后,不論是添加節點還是刪除節點,都會使樹變成非高度平衡的狀態,這種非高度平衡的狀態有4種:

1.LL

LL是left-left,可以理解為:首先它不平衡,其次根節點的左子樹比右子樹高,並且根節點的左子樹的左子樹比根節點的左子樹的右子樹高。(從上到下都是左邊高)

2.LR

LR是left-right,可以理解為:首先它不平衡,其次根節點的左子樹比右子樹高,並且根節點的左子樹的右子樹比根節點的左子樹的左子樹高。(從上到下先左高后右高)

3.RR

RR是right-right,可以理解為:首先它不平衡,其次根節點的右子樹比左子樹高,並且根節點的右子樹的右子樹比根節點的右子樹的左子樹高。(從上到下都是右邊高)

4.RL

RL是right-left,可以理解為:首先它不平衡,其次根節點的右子樹比左子樹高,並且根節點的右子樹的左子樹比根節點的右子樹的右子樹高。(從上到下先右高后左高)

最后,判斷是哪種形式的非平衡狀態,一定要從不平衡的節點位置看,並不是看4層,比如:

這里只有3層節點,不平衡的節點是20,20的左子樹比右子樹高,10的左子樹比右子樹高,所以是LL。(這里的高定義為節點5的高度為1,空節點的高度為0)

接下來是保持高度平衡的調整策略:

同樣對於4種不同的形式有4種解決方案:

1.LL

這個變換就像是以10為中心,向右旋轉,使10變成根節點,10的左子樹不變,右子樹變成了20,多余出的15正好掛在由於變換失去了左子樹的20的左邊。變換后結點從左到右的順序依然沒有變,所以15是正好掛在20的左邊的。

2.RR

RR與LL形式差不多,只不顧是反着來的。相當於進行一次左旋轉。

RR與LL都只進行一次旋轉即可,而LR與RL需要進行兩次旋轉

3.LR

第一次相當於對5、10、15、17這棵子樹進行了一次RR旋轉,旋轉方式與之前的RR方式相同,就像是以15為中心向左旋轉,旋轉的結果使得整棵樹變成了LL的不平衡形態,然后再按照LL的旋轉方式對整棵樹處理。

4.RL

RL同樣是LR的相反模式,先將22、25、30、40這棵子樹進行LL旋轉,再將整棵樹進行RR旋轉

理解了avl保持平衡從方式后,就可以用代碼來實現了

Python實現

我們使用AVL對上一篇文章中的有序映射進行優化

因為AVL依賴於節點的高度,所以首先要重寫一下Node類:


class AvlTree(OrderedMap):

    class Node(OrderedMap.Node):
        def __init__(self, element, parent=None, left=None, right=None):
            super().__init__(element,parent,left,right)
            self.height = 0

        def left_height(self):
            return self.left.height if self.left is not None else 0

        def right_height(self):
            return self.right.height if self.right is not None else 0

接下來定義4中調整的非公開方法


def _left_left(self,p):
    this = p.node        # 有變化的就4個節點
    left = this.left
    parent = this.parent
    left_right = this.left.right
    if parent is not None:
        if this is parent.left:
            parent.left = left
        else:
            parent.right = left
    else:
        self._root = left
    this.parent = left
    left.parent = parent
    this.left = left_right
    left.right = this
    if left_right is not None:
        left_right.parent = this

def _right_right(self,p):
    this = p.node                 # 有變化的就4個節點
    right = this.right
    parent = this.parent
    right_left = this.right.left
    if parent is not None:
        if this is parent.left:
            parent.left = right
        else:
            parent.right = right
    else:
        self._root = right
    this.parent = right
    right.parent = parent
    this.right = right_left
    right.left = this
    if right_left is not None:
        right_left.parent = this

def _left_right(self,p):
    self._right_right(self.left(p))
    self._left_left(p)

def _right_left(self,p):
    self._left_left(self.right(p))
    self._right_right(p)

然后是用於平衡二叉樹的方法,也就是根據情況調用上邊那4種策略


def _isbalanced(self,p):
    """判斷節點是否平衡"""

    return abs(p.node.left_height() - p.node.right_height()) <= 1

def _recompute_height(self,p):
    """重新計算高度"""
    p.node.height = 1 + max(p.node.left_height(),p.node.right_height())

def _rebalanced(self,p):
    while p is not None:
        if self._isbalanced(p):
            self._recompute_height(p)
            p = self.parent(p)
        else:

            if p.node.left_height()>p.node.right_height() and p.node.left.left_height()>p.node.left.right_height():
                # LL的情況,只有自己和左孩子的高度可能變化
                self._left_left(p)
            elif p.node.right_height()>p.node.left_height() and p.node.right.right_height()>p.node.right.left_height():
                # RR的情況,只有自己和右孩子的高度可能變化
                self._right_right(p)
            elif p.node.left_height()>p.node.right_height() and p.node.left.left_height()<p.node.left.right_height():
                # LR的情況,只有自己和左孩子和左孩子的右孩子的高度可能變化
                left = self.left(p)
                self._left_right(p)
                self._recompute_height(left)
            else:
                # RL的情況,只有自己和右孩子和右孩子的左孩子的高度可能變化
                right = self.right(p)
                self._right_left(p)
                self._recompute_height(right)
            while p is not None:
                # 調整所有p的祖先的高度
                self._recompute_height(p)
                p = self.parent(p)

然后把方法封裝成刪除時和插入時的兩個方法,雖然執行的內容是相同的


def _rebalanced_insert(self,p):
    """插入時的平衡調整"""
    self._rebalanced(p)

def _rebalanced_delete(self, p):
    """刪除時的平衡調整"""
    self._rebalanced(p)

最后重寫一下setitem方法與刪除時調用的方法


def __setitem__(self, k, v):
    """優化setitem"""
    if self.is_empty():
        leaf = self.add_root(self._Item(k, v))
    else:

        p = self._subtree_search(self.root(), k)
        if p.key() == k:
            p.element().value = v
            return
        else:
            item = self._Item(k, v)
            if p.key() < k:
                leaf = self.add_right(p, item)
            else:
                leaf = self.add_left(p, item)
    self._rebalanced_insert(leaf)

def mapdelete(self, p):
    if self.left(p) and self.right(p):  # 兩個孩子都有的時候
        replacement = self._subtree_last_position(
            self.left(p))  # 用左子樹最右位置代替
        self.replace(p, replacement.element())
        p = replacement
    parent = self.parent(p)
    self.delete(p)
    self._rebalanced_delete(parent)

在實現4種平衡策略時,一定要記着將整棵樹的根節點更新,不然遍歷的時候,根節點指的就不是真正的根節點了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM