美文网首页DATA STRUCTURE
python数据结构教程 Day14

python数据结构教程 Day14

作者: XaviSong | 来源:发表于2020-08-04 12:23 被阅读0次

本章内容

  1. 平衡二叉树定义
  2. AVL树实现

一、平衡二叉树(AVL树定义)

能够在key插入时一直保持平衡的二叉查找树,AVL树的实现中,需要对每个节点跟踪 “平衡因子balance factor”参数。

平衡因子:

平衡因子是根据节点的左右子树的高度来定义的,确切地说,是左右子树高度差:
balanceFactor = height(leftSubTree) − height(rightSubTree)
如果平衡因子大于0,称为“左重left-heavy” , 小于零称为“右重right-heavy” 平衡因子等于0,则称作平衡。

如果一个二叉查找树中每个节点的平衡因 子都在-1,0,1之间,则把这个二叉搜索树称为平衡树。在平衡树操作过程中,有节点的平衡因子超出此范围,则需要一个重新平衡的过程,同时要保持BST的性质。

二、AVL树的实现

首先,作为BST,插入新key必定以叶节点形式插入到AVL树中。

会引起什么反应?
叶节点的平衡因子:

其本身无需重新平衡。

父节点的平衡因子:
  1. 作为左子节点插入,则父节点平衡因子会增加1;
  2. 作为右子节点插入,则父节点平衡因子会减少1。

这种影响可能随着其父节点到根节点的路径一直传递上去,直到: 传递到根节点为止; 或者某个父节点平衡因子被调整到0,不再影响上层节点的平衡因子为止。

主要手段:将不平衡的子树进行旋转rotation

视“左重”或者“右重”进行不同方向的旋转,同时更新相关父节点引用,更新旋转后被影响节点的平衡因子。

最简单情况

如图,是一个“右重”子树A的左旋转 (并保持BST性质) 将右子节点B提升为子树的根,将旧根节点A作为新根节点B的左子节点,如果新根节点B原来有左子节点,则将此节点设 置为A的右子节点(A的右子节点一定有空)

稍微复杂情况:

旋转后,新根节点将旧根节点作为右子节点,但是新根节点原来已有右子节点,需要将原有的右子节点重新定位。 原有的右子节点D改到旧根节点E的左子节点同样,E的左子节点在旋转后一定有空

最复杂的情况:

下图的“右重”子树,单纯的左旋转无法实现平衡 左旋转后变成“左重”了 “左重”再右旋转,还回到“右重”。

所以,在左旋转之前检查右子节点的因子 如果右子节点“左重”的话,先对它进行右旋转 再实施原来的左旋转。同样,在右旋转之前检查左子节点的因子 如果左子节点“右重”的话,先对它进行左旋转 再实施原来的右旋转。

如何调整平衡因子:
A、C、E三个的平衡因子没有变化
新B= hA- hC
旧B= hA- 旧hD
而:
旧hD= 1+ max(hC, hE),所以旧B= hA- (1+ max(hC, hE))
新B- 旧B= 1+ max(hC, hE)- hC
新B= 旧B+ 1+ max(hC, hE)- hC;把hC移进max函数里就有
新B= 旧B+ 1+ max(0, -旧D) <==> 新B= 旧B+ 1- min(0, 旧D)
代码实现:
class AVLTree(BinarySearchTree):
    def __init__(self):
        '''
        继承于BST,重写方法并在此基础上添加方法修改
        '''
        super().__init__()

    def _put(self, key, val, currentNode):
        '''
        子类重写的父类的方法
        '''
        if key < currentNode.key:
            if currentNode.hasLeftChild():
                self._put(key, val, currentNode.leftChild)
            else:
                currentNode.leftChild = TreeNode(key,val,parent = currentNode)
                self.updateBalance(currentNode.leftChild) # 调整平衡因子
        else:
            if currentNode.hasRightChild():
                self._put(key, val, currentNode.ritghchild)
            else:
                currentNode.ritghchild = TreeNode(key, val, parent=currentNode)
                self.updateBalance(currentNode.ritghchild)

    def updateBalance(self, node):
        if node.balanceFactor > 1 or node.balanceFactor < -1:
            self.rebalance(node)
            return
        if node.parent != None:
            if node.isLeftChild():
                node.parent.balanceFactor += 1
            elif node.isRightChild():
                node.parent.balanceFactor -= 1
            if node.parent.balanceFactor !=0 :
                self.updateBalance(node.parent)

    def rotateLeft(self,rotRoot):
        '''
        左旋代码
        右旋代码参考同样的流程
        '''
        newRoot = rotRoot.ritghchild
        rotRoot.ritghchild = newRoot.leftChild
        if newRoot.leftChild != None:
            newRoot.leftChild.parent = rotRoot
        newRoot.parent = rotRoot.parent
        if rotRoot.isRoot():
            self.root = newRoot
        else:
            if rotRoot.isLeftChild():
                rotRoot.parent.leftChild = newRoot
            else:
                rotRoot.parent.ritghchild = newRoot
        newRoot.leftChild = rotRoot
        rotRoot.parent = newRoot
        # 平衡因子更新的顺序不可调换
        rotRoot.balanceFactor = rotRoot.balanceFactor + 1 - min(newRoot.balanceFactor,0)
        newRoot.balanceFactor = newRoot.balanceFactor + 1 + max(rotRoot.balanceFactor, 0) 

    def rotateRight(self, rotnode):
        pass


    def rebalance(self, node):
        '''
        1、在左旋转之前检查右子节点的因子
        如果右子节点“左重”的话,先对它进行右旋转
        再实施原来的左旋转
        2、在右旋转之前检查左子节点的因子
        如果左子节点“右重”的话,先对它进行左旋转
        再实施原来的右旋转
        '''
        # 右重需要左旋
        if node.balanceFactor < 0:
            if node.ritghchild.balanceFactor > 0:
                # RL
                self.rotateRight(node.ritghchild)
                self.rotateLeft(node)
            else:
                self.rotateLeft(node)
        elif node.balanceFactor > 0:
            if node.leftChild.balanceFactor < 0:
                self.rotateLeft(node.leftChild)
                self.rotateRight(node)
            else:
                self.rotateRight(node)

AVL树总结:

需要插入的新节点是叶节点,更新其所有父节点 和祖先节点的代价最多为O(log n) 如果插入的新节点引发了不平衡,重新平衡最多 需要2次旋转,但旋转的代价与问题规模无关, 是常数O(1) 所以整个put方法的时间复杂度还是O(log n)。

相关文章

网友评论

    本文标题:python数据结构教程 Day14

    本文链接:https://www.haomeiwen.com/subject/bdairktx.html