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)。  

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