python數據結構學習筆記-2016-12-25-01-2-3 樹

       14.4 2-3 樹

       2-3 樹是一種平衡樹,其形狀定義如下:

  • 每一個結點可儲存1個或2個關鍵碼,分別以k1和k2表示;
  • 每一個結點有2~3個子結點,分別爲左子結點、中子結點和右子結點;
  • 所有的葉結點都在同一層;
  • 每一個內結點如果只有一個關鍵碼,則其只有兩個子結點,如果有兩個關鍵碼,則其有三個子結點。
       對於每一個內結點來說,有:


  • 所有小於該結點第一個關鍵碼的關鍵碼都在左子樹中;
  • 如果結點只含有兩個子結點,則大於該結點關鍵碼的都在中子樹中;
  • 如果結點含有三個子結點,則在該結點兩個關鍵碼之間的關鍵在中子樹中,所有大於該結點第二個關鍵碼的關鍵碼在右子樹種。

        

        14.4.1 查找

        2-3樹的查找與二叉查找樹類似,通過比較目標關鍵字與結點處的關鍵碼,如果位於該結點則表明目標關鍵字在2-3樹中,如果不在則定位該結點的正確子樹,繼續上述過程。要注意到的是,如果目標關鍵字不存在,那麼指針最後肯定停留在葉結點上。

         

        14.4.2 插入

        2-3樹的插入較二叉查找樹更爲複雜,但基本思路是相同的,首先是進行查找,判斷目標關鍵字是否已存在樹中,如果存在,則修改相應值即可,如果不存在,由前面的查找操作可知,指針最後停留在葉結點上,首先要確定的是該葉結點的是否還能儲存目標關鍵字,如果可以存儲,那麼還要比較其與該葉結點已存在的關鍵碼的大小,根據大小來插入。


        分裂葉結點

        但是如果該葉結點的兩個數據域都滿了,情況就變複雜了。之前的二叉查找樹是在原結點處分支出新結點,但是2-3樹不同,它的所有葉結點都在同一層。因此,只能將該葉結點分裂,與此同時在同一層創建新的一個葉結點。

        這一過程具體如下,首先在同一層創建一個新結點,然後將要插入的關鍵碼與原葉結點的兩個關鍵碼比較,最小的關鍵碼儲存在原結點中,最大的關鍵碼存在新創建的結點中,而處在中間的關鍵碼則上溢至原葉結點的父結點中,即插入父結點中。


          處於中間的關鍵碼上溢至父結點後,其插入方式與插入葉結點類似,尤其是父結點只有一個關鍵碼的情況。


           若父結點也已經有兩個關鍵碼,則重複上述分裂結點的過程,唯一不同的是要改變父結點與子結點的連接。


           如果要分裂根結點,需要創建一個新的根結點,用來儲存上溢的關鍵碼。


          實現

#-*-coding: utf-8-*-

# 2-3樹實現的映射ADT

class Tree23Map(object):
    def __init__(self):
        self._root = None
        self._size = 0

    def _23Search(self, subtree, target):
        if subtree is None:
            return
        elif subtree.hasKey(target):
            return subtree.getData(target)
        else:
            branch = subtree.getBranch(target)
            return self._23Search(branch, target)

    def _23Insert(self, key, newitem):
        if self._root is None: # 空樹情形
            self._root = _23TreeNode(key, newitem)
        else: # 查找正確的葉結點插入
            (pKey, pData, pRef) = self._23SplitNode(self._root, key, newitem)
            # 判斷葉結點是否分裂
            if pKey is not None:
                newRoot = _23TreeNode(pKey, pData)
                newRoot.left = self._root
                newRoot.middle = pRef
                self._root = newRoot
    
    # 向2-3樹中插入結點的遞歸方法,這一方法也是關於根結點的兩種特殊情況,首先是空樹,創建結點作爲根結點,其次是分裂根結點
    def _23RecInsert(self, subtree, key, newitem):
        # 首先確保目標關鍵字不在2-3樹中
        if subtree.hasKey(key):
            return (None, None, None)
        # 判斷傳入結點subtree是否是葉結點
        elif subtree.isALeaf(): # 是葉結點,就直接創建新結點,然後插入2-3樹
            return self._23AddToNode(subtree, key, newitem, None)
        # 如果傳入結點subtree是內結點
        else:
            branch = subtree.getBranch(key) # 確定目標關鍵碼要插入的位置位於傳入結點subtree的哪一個分支
            (pKey, pData, pRef) = self._23RecInsert(branch, key, newitem) # pKey、pData和pRef表示上溢關鍵字、相應值以及新創建的結點
            if pKey is None:
                return (None, None, None)
            else:
                return self._23AddToNode(subtree, pKey, pData, pRef)

    # 向一個結點中插入關鍵字,其中pRef表示新創建的子結點
    def _23AddToNode(self, subtree, key, data, pRef):
        # 判斷結點subtree是否已滿
        if subtree.isFull(): # 滿了,就分裂結點
            return self._23SplitNode(subtree, key, data, None)
        # 如果沒滿,就找出正確插入位置
        else:
            if key < subtree.key1: 
                subtree.key2 = subtree.key1
                subtree.data2 = subtree.data1
                subtree.key1 = key
                subtree.data1 = data
                if pRef is not None: # 上溢關鍵字插入父結點的第一種情況
                    subtree.right = subtree.middle
                    subtree.middle = pRef
            else:
                subtree.key2 = key
                subtree.data2 = data
                if pRef is not None: # 上溢關鍵字插入父結點的第二種情況
                    subtree.right = pRef
            return (None, None, None)

    # 分裂非根結點,返回一個元組,包括上溢關鍵字pKey、上溢關鍵字的相應值pData以及新創建的結點newnode,其中pRef(新創建的子結點)不爲None時,表示分裂內結點。
    def _23SplitNode(self, node, key, data, pRef):
        # 創建新的空結點
        newnode = _23TreeNode(None, None)
        # 接下來是確定上溢關鍵字
        # 目標關鍵字小於插入結點的第一個關鍵字
        if key < node.key1:
            pKey = node.key1
            pData = node.data1
            node.key1 = key
            node.data1 = data
            newnode.key1 = node.key2
            newnode.data1 = node.data2
            if pRef is not None: # 上溢關鍵字插入父結點的第一種情況
                newnode.left = node.middle
                newnode.middle = node.right
                node.middle = pRef
        # 目標關鍵字介於插入結點的兩個關鍵字之間
        elif key < node.key2:
            pKey = key
            pData = data
            newnode.key1 = node.key2
            newnode.data1 = node.data2
            if pRef is not None: # 上溢關鍵字插入父結點的第二種情況
                newnode.left = pRef
                newnode.middle = node.right
        # 目標關鍵字大於插入結點的第二個關鍵字
        else:
            pKey = node.key2
            pData = node.data2
            newnode.key1 = key
            newnode.data1 = data
            if pRef is not None:
                newnode.left = node.right
                newnode.middle = pRef
        node.key2 = None
        node.data2 = None 
        return (pKey, pData, newnode)

# 2-3樹的儲存類
class _23TreeNode(object):
    def __init__(self, key, data):
        self.key1 = key
        self.key2 = None # 第二個數據域初始化爲空
        self.data1 = data
        self.data2 = None
        self.left = None
        self.middle = None
        self.right = None

    # 判斷葉結點
    def isALeaf(self):
        return self.left is None and self.middle is None and self.right is None

    # 判斷該結點數據域是否已滿
    def isFull(self):
        return self.key2 is not None

    # 判斷目標關鍵碼是否位於該結點
    def hasKey(self, target):
        if (target == self.key1) or (self.key2 is not None and target == self.key2):
            return True
        else:
            return False

    # 假定目標關鍵碼在該結點,返回對應值
    def getData(self, target):
        if target == self.key1:
            return self.data1
        elif self.key2 is not None and target == self.key2:
            return self.data2
        else:
            return None

    # 如果目標關鍵碼不在該結點,判斷其在哪個子樹中
    def getBranch(self, target):
        if target < self.key1:
            return self.left
        elif self.key2 is None:
            return self.middle
        elif target < self.key2:
            return self.middle
        else:
            return self.right


        14.4.3 2-3樹的效率

        2-3樹的最壞情況是每一個結點只有一個值,兩個子結點時,顯然想要遍歷整個2-3樹的時間複雜度是O(n),查找操作依賴於樹的高度,而2-3樹的最大高度就是log n,所以時間複雜度是O(log n)。

        插入操作和刪除操作是類似的,都是先進行查找操作,然後根據情況插入。最壞情況是,每一層都需要分裂結點,直到根結點爲止,而分裂結點所需時間顯然是固定時間的,所以插入操作的時間複雜度是O(log n)。  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章