深入理解平衡二叉樹AVL與Python實現
前言
上一篇文章討論的二叉搜尋樹(見 https://www.linuxidc.com/Linux/2019-02/156830.htm ),其時間複雜度最好的情況下是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種平衡策略時,一定要記著將整棵樹的根節點更新,不然遍歷的時候,根節點指的就不是真正的根節點了。
Linux公社的RSS地址 : https://www.linuxidc.com/rssFeed.aspx
本文永久更新連結地址: https://www.linuxidc.com/Linux/2019-02/156829.htm