AVL树

1. AVL树的概念

  AVL树又称为高度搜索树,它是一个特殊的二叉搜索树,当元素接近于有序的时候,二叉树也会变成一个单链树,所以AVL树就是平衡二叉搜索树,当插入一个节点,树的任意一个左右子树的高度差都<=1,称为AVL树

  

 

 

   上图左边的二叉树的每个节点的左右子树的最大高度差都不超过1,而右边的二叉树的节点7的左右子树的高度差为2,不符合AVL树的定义

 

2. 旋转算法:左左右,右右左,左右左右,右左右左

  1. 由于对二叉树的K的右孩子的右子树插入导致不平衡:左旋

   

 

  2. 由于对二叉树的K的左孩子的左子树插入导致不平衡:右旋

   

 

   3. 由于二叉树对K的右孩子的左子树插入导致不平衡:右旋→左旋

   

 

 

  4. 由于二叉树对K的左孩子的右子树插入导致不平衡:左旋→右旋

   

 

3. AVL数的插入实现 

复制代码
from 二叉搜索树 import *
# 继承
class AVLNode1(BiTreeNode):
    def __init__(self, item):
        BiTreeNode.__init__(self, item)
        self.bf = 0

# AVL树,继承二叉搜索树,代码在二叉树章
class AVLtree(BiSelectTree):
    def __init__(self, li=None):
        BiSelectTree.__init__(self, li)

class AVLNode:
    def __init__(self, item):
        self.item = item
        self.lchild = None  # 左孩子
        self.rchild = None  # 右孩子
        self.parent = None  # 父节点
        self.bf = 0         # balance factor平衡因素,左右子树高度差

class AVLtree:
    def __init__(self, li=None):
        self.root = None
        if li:
            for val in li:
                self.insert(val)

    # 要配合图理解
    # 左旋函数,即插入右子树的右孩子,p不平衡节点,c是p的右子树
    def rotate_left(self, p, c):
        s2 = c.lchild       # 存c的左孩子,即没有变化的节点
        p.rchild = s2       # 旋转之后p是c的左孩子,p的右孩子等于c的左孩子
        # 当c为叶子节点,没有左右孩子,则不存在s2,所以需要判断一下
        if s2:
            s2.parent = p

        c.lchild = p        # 旋转之后,c的左孩子变成了他的父节点
        p.parent = c        # p的父节点就成了他的子节点

        p.bf = 0            # 因为s1和s2是相同的深度,如果深度不同,那原来的树不符合AVL树
        c.bf = 0            # 因为s2和s3相差了一个深度,当p加入左节点后,这两个深度相同
        return c            # 返回根节点,用来和旋转前的父节点连接

    # 右旋函数,即插入左子树的左孩子,和左旋函数一样的原理
    def rotate_right(self, p, c):
        s2 = c.rchild       # 记录左子树的右孩子
        p.lchild = s2       # 旋转之后,p和c的位置交换,p的左孩子等于原来的c的右孩子
        # 非叶子节点判断
        if s2:
            s2.parent = p
        # 将p和c连接起来
        c.rchild = p
        p.parnet = c
        # 和左旋一样的道理,如果是平衡二叉树,必须符合左右子树深度差小于1,插入之后深度相同
        p.bf = 0
        c.bf = 0
        return c

    # 右旋-左旋函数:插入右子树的左孩子,p是c的父节点,c是p的左孩子
    def rotate_right_left(self, p, c):
        g = c.lchild
        # 分别存一下g的两个孩子,s2比g小比p大,所以旋转之后会在平衡节点的g的左子树的p的右子树
        s2 = g.lchild
        s3 = g.rchild
        # 判断s2是否存在
        if s2:
            s2.parent = p
            # p.rchild = s2   # 这个可以不写,因为它必定是p的右孩子
        g.lchild = p
        p.parent = g
        # 判断s3是否存在
        if s3:
            s3.parent = c
            # c.lchild = s3   # 必定是c的左孩子
        g.rchild = c
        c.parent = g

        # bf等于左子树深度减去右子树深度
        if g.bf > 0:    # 即左子树比右子树大1层,设左孩子有,右孩子没有
            p.bf = 0
            c.bf = -1
        elif g.bf < 0:  # 即右子树比左子树大1层,设想右孩子有,左孩子没有
            p.bf = 1
            c.bf = 0
        else:           # 等于0,左右孩子都存在
            p.bf = 0
            c.bf = 0
        return g

    # 左旋-右旋函数:插入左子树的右孩子, p是c的父节点,c是p的左孩子
    def rotate_left_right(self, p, c):
        g = c.rchild
        # 分别存一下g的两个孩子,32比g大比p小,所以旋转之后会在平衡节点的g的右子树的p的左子树
        s2 = g.lchild
        s3 = g.rchild
        # 判断s2是否存在
        if s2:
            s2.parent = c
            c.rchild = s2  # 这个可以不写,因为它必定是p的右孩子
        g.lchild = c
        c.parent = g
        # 判断s3是否存在
        if s3:
            s3.parent = p
            p.lchild = s3  # 必定是c的左孩子
        g.rchild = p
        p.parent = g

        # 更新bf
        if g.bf > 0:    # 左孩子有,右孩子没有
            c.bf = 0
            p.bg = -1
        elif g.bf < 0:  # 右孩子有,左孩子没有
            c.bf = 1
            p.bf = 0
        else:
            c.bf = 0
            p.bf = 0
        return g

    # 插入节点
    def insert(self, val):
        pointer = self.root     # 指针指向节点
        if not pointer:
            self.root = AVLNode(val)   # 空树直接插入节点
            return
        while True:
            # 小于指针节点,则插入左边
             if val < pointer.item:
                 if pointer.lchild:     # 如果左孩子存在,则指向左孩子
                    pointer = pointer.lchild
                 else:      # 左孩子不存在,则直接插入,并指定父节点
                     pointer.lchild = AVLNode(val)
                     pointer.lchild.parent = pointer
                     node = pointer.lchild      # 存储插入节点,需要移动
                     break
             # 大于指针节点,则插入右边,逻辑和小于一致
             elif val > pointer.item:
                 if pointer.rchild:
                     pointer = pointer.rchild
                 else:
                     pointer.rchild = AVLNode(val)
                     pointer.rchild.parent = pointer
                     node = pointer.rchild
                     break
             else:
                 return

        # 更新bf
        while node.parent:      # 说明不是根节点
            if node.parent.lchild == node:    # 说明插入了左子树,左边深度+1
                # 情况1:大于0,说明node父节点的左子树比右子树大,插入左子树更新之后node.parent的bf=2,需要进行旋转
                if node.parent.bf > 0:
                    g_parent = node.parent.parent  # 原来的平衡节点的父节点
                    old_g = node.parent         # 旋转之前的平衡节点,即比较的那个顶点
                    if node.bf < 0:          # 说明插入node的右孩子,即左子树的右孩子
                        # n为旋转之后的平衡点
                        n = self.rotate_left_right(node.parent, node)
                    # 这里bf大于等于0,大于0指叶子结点,插入node的左子树后,node的左边比右边多1层,等于0是,原来右比左大1,插入后左右子树相同
                    else:
                        n = self.rotate_right(node.parent, node)
                # 情况2:,小于0,说明右子树比左子树大,插入左边更新之后左右子树相同,即node.parent的bf=0,则二叉搜索树不需进行旋转
                elif node.parent.bf < 0:
                    node.parent.bf = 0
                    break
                # 情况3,等于0,说明左右子树原来相等,插入左子树后, node.parent的bf=1,符合AVL树,不需要进行转换
                else:
                    node.parent.bf = 1
                    node = node.parent      # 有插入节点往根节点检查bf值,循环执行
                    continue                # 跳过后续执行前面的循环

            # # 说明插入了右子树,右边深度+1
            else:
                # 情况1:小于0,说明node父节点的右子树比左子树大,插入右子树更新之后node.parent的bf=2,需要进行旋转
                if node.parent.bf < 0:
                    g_parent = node.parent.parent      # 原来的平衡节点的父节点
                    old_g = node.parent             # 旋转之前的平衡节点,即比较的那个顶点
                    # 大于0即1,就是左子树比右子树大,即插入了左边,执行选择,口诀:右左右左
                    if node.bf > 0:
                        n = self.rotate_right_left(node.parent, node)
                    # 这里bf小于等于0,插入之后小于0,则原来为叶子结点,插入node的右子树后,node的右边比左边多1层,等于0是,原来左比右大1,插入后左右子树相同
                    else:
                        n = self.rotate_left(node.parent, node)    # 口诀:右右左
                # # 情况2:,大于0,说明左子树比右子树大,插入右子树更新之后左右子树相同,即node.parent的bf=0,则二叉搜索树不需进行旋转
                elif node.parent.bf > 0:
                    node.parent.bf = 0
                    break
                # 情况3,等于0,说明左右子树原来相等,插入右子树后, node.parent的bf=1,符合AVL树,不需要进行转换
                else:
                    node.parent.bf = -1
                    node = node.parent      # 往根节点进行查找bf是否存在大于1的情况
                    continue

            # 连接旋转后的子树
            n.parent = g_parent            # g是旋转之前的平衡点的父节点,n是旋转之后的平衡点
            if g_parent:
                if old_g == g_parent.lchild:       # x为旋转之前的平衡点,是它父节点的左孩子,则n为左孩子,否则为右孩子
                    g_parent.lchild = n
                else:
                    g_parent.rchild = n
                break
            # g不存在说明旋转之后的平衡点就是整棵树的根节点
            else:
                self.root = n
                break

    # 前序遍历
    def pro_order(self, root):
        if root:
            print(root.item,end=',')
            self.pro_order(root.lchild)
            self.pro_order(root.rchild)

    # 中序遍历
    def cen_order(self, root):
        if root:
            self.cen_order(root.lchild)
            print(root.item, end=',')
            self.cen_order(root.rchild)

    # 后续遍历
    def later_order(self, root):
        if root:
            self.later_order(root.lchild)    # 先找左边的子节点
            self.later_order(root.rchild)   # 在找优点的子节点
            print(root.item, end=',')

tree = AVLtree([9,8,7,6,5,4,3,2,1])
tree.pro_order(tree.root)           # 继承方法,前序查询 6,4,2,1,3,5,8,7,9
print("")
tree.cen_order(tree.root)           # 继承方法,中序查询 1,2,3,4,5,6,7,8,9
print("")
tree.later_order(tree.root)         # 继承方法,后序查询 1,3,2,5,4,7,9,8,6
复制代码
posted @   无敌小豆包  阅读(45)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示