1. 程式人生 > >分步圖解平衡二叉樹的插入過程(Python實現)

分步圖解平衡二叉樹的插入過程(Python實現)

一、基本概念:

平衡二叉樹:是一種特殊的二叉排序樹,它或者為空樹,或者每個結點的左右子樹都是平衡二叉樹,也就是每個結點的左右子樹的高度之差只能是-1,0,1三種情況。

平衡二叉樹又稱AVL樹,是由蘇聯的Georgy Adelson-Velsky和E.M.Landis發明的,並以他們的名字命名。與之類似的還有紅黑樹、B樹等。

平衡二叉樹的平衡狀況由平衡因子(Balance Factor,BF)來衡量。平衡因子定義為當前結點的左子樹高度減去右子樹的高度之差,其可能取值只有-1,0,1。葉結點的BF都是0。如果能維持平衡二叉樹的結構,檢索操作就能在O(log n)時間內完成。

最小不平衡子樹:距離插入結點最近的,且平衡因子的絕對值大於1的結點為根的子樹。(不平衡即失衡,指BF超出合法值)

最小非平衡子樹:包含插入結點位置,其根結點的BF是1或-1的最小子樹。(非平衡指BF非0,但BF在合法值範圍內)

(此處這兩個概念如有錯誤,歡迎指正)

在下圖中,原來結點4的BF=1,插入結點3後,結點4的BF=2。結點4是距離插入結點3最近的且BF絕對值大於1的結點,所以以結點4為根的子樹就是最小不平衡子樹。

二、插入操作的不同情況

1、如果在查詢插入位置的過程中,所有途經結點的BF值均為0,那麼插入新的結點後,不會導致這些途經結點失衡,只會讓它們的BF值從0變為1或者-1。

如下圖所示,插入結點3,查詢時途經結點為5,4和2,其BF只是從原來的0都變為了1或-1,整棵樹仍然是平衡的。

2、如下圖,插入結點4.7。左圖中最小非平衡子樹以結點4為根結點a,結點4.7被插入到這棵子樹較低的子樹中,此時這棵子樹還是平衡的,只需要修改這棵子樹的從根結點直至插入結點路徑上所有結點的BF值。

以上兩種情況都不需要調整樹的結構

3、需要調整樹結構的情況又分為了四種情況:

(1)LL型調整:a的左子樹較高,新結點插入在a的左子樹的左子樹。進行右旋轉。

在下圖的圖a中,a是最小非平衡子樹的根,b的BF一定是0(否則a就不是最小非平衡子樹的根了)。結點2被插入到了a的左子樹的左子樹,需要進行LL型調整:將結點2-3-4-5-8看做一條可以轉動的鏈子,將其向右旋轉(順時針)一個結點,然後將原來b結點的右子樹,接到a結點的左子結點上,調整完成。

再說明一下插入結點的位置:插入結點不必像上圖一樣,必須插在某個結點的左子結點,也可以像下圖一樣,插在某個結點的右子結點,調整的方法還是一樣的。這也是定義中說:新結點插入在a的左子樹的‘左子樹’,而不是左子樹的左子結點的原因。

@staticmethod    
    def LL(a, b):
        a.left = b.right # 將b的右子樹接到a的左子結點上
        b.right = a #將a樹接到b的右子結點上
        a.bf = b.bf = 0 #調整a、b的bf值。
        return b

(2)RR型調整:a的右子樹較高,新結點插入在a的右子樹的右子樹。進行左旋轉。

RR型調整與LL型正好是對稱的,操作步驟類似。在下圖的圖a中,a是最小非平衡子樹的根,b的BF一定是0。結點9被插入到了a的右子樹的右子樹,需要進行RR型調整:同樣地,將結點4-5-6-8-9看做一條可以轉動的鏈子,將其向左旋轉(逆時針)一個結點,然後將原來b結點的左子樹,接到a結點的右子結點上,調整完成。

同樣地,插入結點也可以插入在結點8的左子結點處,調整步驟是一樣的。

@staticmethod 
    def RR(a, b):
        a.right = b.left
        b.left = a
        a.bf = b.bf = 0
        return b

(3)LR型調整:a的左子樹較高,新結點插入在a的左子樹的右子樹。先進行左旋轉,再進行右旋轉。

在下圖的圖a中,a是最小非平衡子樹的根,b的BF一定是0,c的BF也一定是0。結點4.1被插入到了a的左子樹的右子樹(圖b中4.1插入到了c結點的左子樹,當然也可以插到c結點的右子樹,其調整過程都是一樣的),需要進行LR型調整。

圖c中,首先將c結點的左右子樹分別摘下來,然後將結點4.5-4-3-2看做一條可以轉動的鏈子,對其進行左旋轉(逆時針)一個結點,就得到了圖d,然後再將結點2-3-4-4.5-5-8-9看做一條轉動的鏈子,將其進行右旋轉(順時針)一個結點,就得到了圖e。

最後將原來c結點的左子樹接到b結點的右子結點上,將原來c結點的右子樹接到a結點的左子結點上,調整完成。

@staticmethod 
    def LR(a,b):
        c = b.right
        a.left, b.right = c.right, c.left
        c.left, c.right = b, a
        if c.bf == 0: #c本身就是插入點
            a.bf = b.bf = 0
        elif c.bf == 1: #插在c的左子樹
            a.bf = -1
            b.bf = 0
        else:           #插在c的右子樹
            a.bf = 0
            b.bf = 1
        c.bf = 0
        return c

(4)RL型調整:a的右子樹較高,新結點插入在a的右子樹的左子樹。先進行右旋轉,再進行左旋轉。

RL型調整與LR型正好是對稱的,操作步驟類似。在下圖的圖a中,a是最小非平衡子樹的根,b的BF一定是0,c的BF也一定是0。結點5.5被插入到了a的右子樹的左子樹(圖b中5.5插入到了c結點的左子樹,當然也可以插到c結點的右子樹,其調整過程都是一樣的),需要進行RL型調整。

圖c中,首先將c結點的左右子樹分別摘下來,然後將結點7-9-10-11看做一條可以轉動的鏈子,對其進行右旋轉(順時針)一個結點,就得到了圖d,然後再將結點3-4-5-7-9-10-11看做一條轉動的鏈子,將其進行左旋轉(逆時針)一個結點,就得到了圖e。

最後將原來c結點的左子樹接到a結點的右子結點上,將原來c結點的右子樹接到b結點的左子結點上,調整完成。

@staticmethod 
    def RL(a, b):
        c = b.left
        a.right, b.left = c.left, c.right
        c.left, c.right = a, b
        if c.bf == 0:
            a.bf = b.bf = 0
        elif c.bf == 1:
            a.bf = 0
            b.bf = -1
        else:
            a.bf = 1
            b.bf = 0
        c.bf = 0
        return c

平衡二叉樹的插入操作的複雜度是O(log n)

最後:用平衡二叉樹來實現一個字典類

首先,AVL樹結點類需要增加一個bf域。

class AVLNode(BinTNode):
    def __init__(self, data):
        BinTNode.___init__(self,data)
        self.bf = 0

其次,AVL數是一種二叉排序樹,所以可以直接繼承二叉排序樹的方法。

class DictAVL(DictBinTree):
    def __init__(self, data):
        DictBinTree.___init__(self)

插入操作的實現:

def insert(self, key, value):
        a = p = self.root
        if a is None: #如果根結點為空,則直接將值插入到根結點
            self.root = AVLNode(Assoc(key, value))
            return
        a_father, p_father = None   #a_father用於最後將調整後的子樹接到其子結點上
        while p is not None: #通過不斷的迴圈,將p下移,查詢插入位置,和最小非平衡子樹
            if key == p.data.key: #如果key已經存在,則直接修改其關聯值
                p.data.value = value
                return
            if p.bf != 0: #如果當前p結點的BF=0,則有可能是最小非平衡子樹的根結點
                a_father, a, = p_father, p
            p_father = p
            if key < p.data.key:
                p = p.left
            else:
                p = p.right
    
        #上述迴圈結束後,p_father已經是插入點的父結點,a_father和a記錄著最小非平衡子樹
        node = AVLNode(Assoc(key, value))
        if  key < p_father.data.key:
            p_father.left = node
        else:
            p_father.right = node
    
        #新結點已插入,a是最小非平衡子樹的根結點
        if key < a.data.key: #新結點在a的左子樹
            p = b = a.left
            d = 1           #d記錄新結點被 插入到a的哪棵子樹
        else:
            p = b = a.right #新結點在a的右子樹
            d = -1
    
        #在新結點插入後,修改b到新結點路徑上各結點的BF值。調整過程的BF值修改都在子函式中操作
        while p != node:
            if key < p.data.key:
                p.bf = 1
                p = p.left
            else:
                p.bf = -1
                p = p.right
        if a.bf == 0: #如果a的BF原來為0,那麼插入新結點後不會失衡
            a.bf = d
            return
        if a.bf == -d: #如果新結點插入在a較低的子樹裡
            a.bf = 0
            return
    
        #以上兩條if語句都不符合的話,說明新結點被插入在較高的子樹裡,需要進行調整
        if d == 1:                   #如果新結點插入在a的左子樹
            if b.bf == 1:            #b的BF原來為0,如果等於1,說明新結點插入在b的左子樹
                b = DictAVL.LL(a, b)
            else:                    #新結點插入在b的右子樹
                b = DictAVL.LR(a, b)
        else:                        #新結點插入在a的右子樹
            if b.bf == -1:           #新結點插入在b的右子樹
                b = DictAVL.RR(a, b)
            else:                    ##新結點插入在b的左子樹
                b = DictAVL.RL(a, b)
    
        #將調整後的最小非平衡子樹接到原樹中,也就是接到原來a結點的父結點上
        if a_father is None: #判斷a是否是根結點
            self.root = b
        else:
            if a_father == a:
                a_father.left = b
            else:
                a_father.right = b

完整的程式碼如下:

class StackUnderflow(ValueError):   
    pass  
  
class SStack():  
    def __init__(self):  
        self.elems = []  
          
    def is_empty(self):  
        return self.elems == []  
      
    def top(self): #取得棧裡最後壓入的元素,但不刪除  
        if self.elems == []:  
            raise StackUnderflow('in SStack.top()')  
        return self.elems[-1]  
      
    def push(self, elem):  
        self.elems.append(elem)  
          
    def pop(self):  
        if self.elems == []:  
            raise StackUnderflow('in SStack.pop()')  
        return self.elems.pop()

class Assoc: #定義一個關聯類
    def __init__(self, key, value):
        self.key = key #鍵(關鍵碼)
        self.value = value #值
    
    def __lt__(self, other):#Python直譯器中遇到比較運算子<,會去找類裡定義的__lt__方法(less than)
        return self.key < other.key
    
    def __le__(self, other): #(less than or equal to)
        return self.key < other.key or self.key == other.key
    
    def __str__(self):
        return 'Assoc({0},{1})'.format(self.key, self.value) #key和value分別替換前面{0},{1}的位置。

class BinTNode:  
    def __init__(self, dat, left = None, right = None):  
        self.data = dat  
        self.left = left  
        self.right = right 
    
class DictBinTree:  
    def __init__(self, root = None):  
        self.root = root
    
    def is_empty(self):
        return self.root is None
    
    def search(self, key):#檢索是否存在關鍵碼key
        bt = self.root
        while bt is not None:
            entry = bt.data
            if key < entry.key: 
                bt = bt.left
            elif key > entry.key:
                bt = bt.right
            else:
                return entry.value
        return None
        
    def insert(self, key, value):
        bt = self.root
        if bt is None:
            self.root = BinTNode(Assoc(key, value))
            return
        while True:
            entry = bt.data
            if key < entry.key: #如果小於當前關鍵碼,轉向左子樹
                if bt.left is None: #如果左子樹為空,就直接將資料插在這裡
                    bt.left = BinTNode(Assoc(key,value))
                    return
                bt = bt.left
            elif key > entry.key:
                if bt.right is None:
                    bt.right = BinTNode(Assoc(key,value))
                    return
                bt = bt.right
            else:
                bt.data.value = value
                return
    def print_all_values(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty(): #最開始時棧為空,但bt不為空;bt = bt.right可能為空,棧不為空;當兩者都為空時,說明已經全部遍歷完成了
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() #將棧頂元素彈出
            yield bt.data.key, bt.data.value
            bt = bt.right #將當前結點的右子結點賦給bt,讓其在while中繼續壓入棧內
            
    def entries(self):
        bt, s = self.root, SStack()
        while bt is not None or not s.is_empty():
            while bt is not None:
                s.push(bt)
                bt = bt.left
            bt = s.pop() 
            yield bt.data.key, bt.data.value
            bt = bt.right
            
    def print_key_value(self):
        for k, v in self.entries():
            print(k, v)
            
    def delete(self, key):
        #以下這一段用於找到待刪除結點及其父結點的位置。
        del_position_father, del_position = None, self.root #del_position_father是待刪除結點del_position的父結點
        while del_position is not None and del_position.data.key != key: #通過不斷的比較,找到待刪除結點的位置
            del_position_father = del_position
            if key < del_position.data.key:
                del_position = del_position.left
            else:
                del_position = del_position.right
            if del_position is None:
                print('There is no key')
                return
            
        if del_position.left is None: #如果待刪除結點只有右子樹
            if del_position_father is None: #如果待刪除結點的父結點是空,則說明待刪除結點是根結點
                self.root = del_position.right #則直接將根結點置空
            elif del_position is del_position_father.left: #如果待刪除結點是其父結點的左結點
                del_position_father.left = del_position.right #***改變待刪除結點父結點的左子樹的指向
            else:
                del_position_father.right = del_position.right
            return
        
        #如果既有左子樹又有右子樹,或者僅有左子樹時,都可以用直接前驅替換的刪除結點的方式,只不過得到的二叉樹與原理中說明的不一樣,但是都滿足要求。
        pre_node_father, pre_node = del_position, del_position.left
        while pre_node.right is not None: #找到待刪除結點的左子樹的最右結點,即為待刪除結點的直接前驅
            pre_node_father = pre_node
            pre_node = pre_node.right
        del_position.data = pre_node.data #將前驅結點的data賦給刪除結點即可,不需要改變其原來的連線方式

        if pre_node_father.left is pre_node:
            pre_node_father.left = pre_node.left
        if pre_node_father.right is pre_node:
            pre_node_father.right = pre_node.left
        
            
def build_dictBinTree(entries):
    dic = DictBinTree()
    for k,v in entries:
        dic.insert(k,v)
    return dic


class AVLNode(BinTNode):
    def __init__(self, data):
        BinTNode.___init__(self,data)
        self.bf = 0

class DictAVL(DictBinTree):
    def __init__(self, data):
        DictBinTree.___init__(self)
        
    @staticmethod    
    def LL(a, b):
        a.left = b.right # 將b的右子樹接到a的左子結點上
        b.right = a #將a樹接到b的右子結點上
        a.bf = b.bf = 0 #調整a、b的bf值。
        return b
    
    @staticmethod 
    def RR(a, b):
        a.right = b.left
        b.left = a
        a.bf = b.bf = 0
        return b
    
    @staticmethod 
    def LR(a,b):
        c = b.right
        a.left, b.right = c.right, c.left
        c.left, c.right = b, a
        if c.bf == 0: #c本身就是插入點
            a.bf = b.bf = 0
        elif c.bf == 1: #插在c的左子樹
            a.bf = -1
            b.bf = 0
        else:           #插在c的右子樹
            a.bf = 0
            b.bf = 1
        c.bf = 0
        return c
    
    @staticmethod 
    def RL(a, b):
        c = b.left
        a.right, b.left = c.left, c.right
        c.left, c.right = a, b
        if c.bf == 0:
            a.bf = b.bf = 0
        elif c.bf == 1:
            a.bf = 0
            b.bf = -1
        else:
            a.bf = 1
            b.bf = 0
        c.bf = 0
        return c
    
    def insert(self, key, value):
        a = p = self.root
        if a is None: #如果根結點為空,則直接將值插入到根結點
            self.root = AVLNode(Assoc(key, value))
            return
        a_father, p_father = None   #a_father用於最後將調整後的子樹接到其子結點上
        while p is not None: #通過不斷的迴圈,將p下移,查詢插入位置,和最小非平衡子樹
            if key == p.data.key: #如果key已經存在,則直接修改其關聯值
                p.data.value = value
                return
            if p.bf != 0: #如果當前p結點的BF=0,則有可能是最小非平衡子樹的根結點
                a_father, a, = p_father, p
            p_father = p
            if key < p.data.key:
                p = p.left
            else:
                p = p.right
    
        #上述迴圈結束後,p_father已經是插入點的父結點,a_father和a記錄著最小非平衡子樹
        node = AVLNode(Assoc(key, value))
        if  key < p_father.data.key:
            p_father.left = node
        else:
            p_father.right = node
    
        #新結點已插入,a是最小非平衡子樹的根結點
        if key < a.data.key: #新結點在a的左子樹
            p = b = a.left
            d = 1           #d記錄新結點被 插入到a的哪棵子樹
        else:
            p = b = a.right #新結點在a的右子樹
            d = -1
    
        #在新結點插入後,修改b到新結點路徑上各結點的BF值。調整過程的BF值修改都在子函式中操作
        while p != node:
            if key < p.data.key:
                p.bf = 1
                p = p.left
            else:
                p.bf = -1
                p = p.right
        if a.bf == 0: #如果a的BF原來為0,那麼插入新結點後不會失衡
            a.bf = d
            return
        if a.bf == -d: #如果新結點插入在a較低的子樹裡
            a.bf = 0
            return
    
        #以上兩條if語句都不符合的話,說明新結點被插入在較高的子樹裡,需要進行調整
        if d == 1:                   #如果新結點插入在a的左子樹
            if b.bf == 1:            #b的BF原來為0,如果等於1,說明新結點插入在b的左子樹
                b = DictAVL.LL(a, b)
            else:                    #新結點插入在b的右子樹
                b = DictAVL.LR(a, b)
        else:                        #新結點插入在a的右子樹
            if b.bf == -1:           #新結點插入在b的右子樹
                b = DictAVL.RR(a, b)
            else:                    ##新結點插入在b的左子樹
                b = DictAVL.RL(a, b)
    
        #將調整後的最小非平衡子樹接到原樹中,也就是接到原來a結點的父結點上
        if a_father is None: #判斷a是否是根結點
            self.root = b
        else:
            if a_father == a:
                a_father.left = b
            else:
                a_father.right = b
if __name__=="__main__":
    #LL調整
    entries = [(5,'a'),(2.5,'g'),(2.3,'h'),(3,'b'),(2,'d'),(4,'e'),(3.5,'f')]
    dic = build_dictBinTree(entries)
    dic.print_key_value()
    print('after inserting')
    dic.insert(1, 'i') 
    dic.print_key_value()
    
    #LR調整
    entries = [(2.5,'g'),(3,'b'),(4,'e'),(3.5,'f')]
    dic = build_dictBinTree(entries)
    dic.print_key_value()
    print('after inserting')
    dic.insert(3.2, 'i') #LL
    dic.print_key_value()