详解平衡二叉树(AVL树)以及Python实现相关操作

引言

为什么引入平衡二叉树?

\quad \quad 含有n个结点的二叉排序树的平均查找长度ASL和树的形态有关
在这里插入图片描述
在这里插入图片描述

问题: 如何提高形态不均衡的二叉排序树的查找效率?

解决办法: 做平衡化处理,尽量让二叉树的形状均衡,转换成平衡二叉树。

1、定义

平衡二叉树(balanced binary tree)

  • 又称AVL树(Adelson - VelskII and Landis)。

  • 一棵平衡二叉树或者是空树,或者是具有下列性质的二叉排序树

    • 1)子树与子树的高度之差的绝对值小于等于1
    • 2)子树与子树平衡二叉树。

\quad \quad 为了方便起见,给每个结点附加一个数字,给出该结点左子树与右子树的高度差。这个数字称为结点的平衡因子(BF)
平 衡 因 子 = 结 点 左 子 树 的 高 度 − 结 点 右 子 树 的 高 度 平衡因子=结点左子树的高度-结点右子树的高度 =

\quad \quad 根据平衡二叉树的定义,平衡二叉树上所有结点的平衡因子只能是-1、0、1。

在这里插入图片描述
\quad \quad 对于一棵有n个结点的AVL树,其高度保持在 O ( l o g 2 n ) O(log_2n) O(log2n)数量级,ASL也保持在 O ( l o g 2 n ) O(log_2n) O(log2n)数量级。

2、失衡二叉排序树的分析与调整

\quad \quad 当我们在平衡二叉排序树上插入一个结点时,有可能导致失衡,即出现平衡因子绝对值大于1的结点,如:2、-2。

在这里插入图片描述
\quad \quad 如果在一棵AVL树中插入一个新结点后造成失衡,则必须重新调整树的结构,使之恢复平衡。

平衡调整的四种类型

在这里插入图片描述

  • LL型:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。左子树高度大于右子树高度

  • LR型:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

  • RL型:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

  • RR型:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

如何进行平衡调整的四种类型

在这里插入图片描述
\quad \quad AVL树失去平衡之后,可以通过旋转使其恢复平衡。下面分别介绍四种失去平衡的情况下对应的旋转方法。

2.1 LL型—右旋转

\quad \quad 当左子树高度大于右子树高度,应该进行单旋转–右旋转操作:

(1)将根节点的左孩子作为新根节点。
(2)将新根节点的右孩子作为原根节点的左孩子。
(3)将原根节点作为新根节点的右孩子。

【图解过程】:
在这里插入图片描述
在这里插入图片描述

# 右旋转
def right_rotate(node):
    if node is None:
        return
    # 创建新的结点,以当前根结点的值
    new_node = copy.deepcopy(node)
    # 把新结点的右子树设为当前结点的右子树
    new_node.right = node.right
    # 把新结点的左子树设为当前结点的左子树的右子树
    new_node.left = node.left.right
    # 把当前结点的值替换成它的左子结点的值
    node.val = node.left.val
    # 把当前结点的左子树设置成当前结点的左子树的左子树
    node.left = node.left.left
    # 把当前结点的右子结点设置成(指向)新的结点
    node.right = new_node

2.2 RR型—左旋转

\quad \quad 当右子树高度大于左子树高度,应该进行单旋转–左旋转操作:

(1) 将根节点的右孩子作为新根节点。
(2) 将新根节点的左孩子作为原根节点的右孩子。
(3) 将原根节点作为新根节点的左孩子。

【图解过程】:
在这里插入图片描述
在这里插入图片描述

  • 总体代码都是一样的,只是方向调整一下就可以了
# 左旋转
def left_rotate(node):
    if node is None:
        return
    # 创建新的结点,以当前根结点的值
    new_node = copy.deepcopy(node) # 这点最重要了,一定是深拷贝,直接赋值是浅拷贝
    # --不然下面操作到后面会被置空
    # 把新结点的左子树设为当前结点的左子树
    new_node.left = node.left
    # 把新结点的右子树设为当前结点的右子树的左子树
    new_node.right = node.right.left
    # 把当前结点的值替换成它的右子结点的值
    node.val = node.right.val
    # 把当前结点的右子树设置成当前结点的右子树的右子树
    node.right = node.right.right
    # 把当前结点的左子结点设置成(指向)新的结点
    node.left = new_node

2.3 双旋转

\quad \quad 如果它的左子树的右子树的高度 大于 它的左子树的左子树的高度(LR型) 或者它的右子树的左子树的高度 大于 它的右子树的右子树的高度(RL型),不能简单的进行单次的左旋转或者右旋转。见下面例子:

在这里插入图片描述
\quad \quad 左图满足右旋转,于是我们进行右旋转,最终7变成了新的根结点,8和9挂到哦了10结点的左边,发现高度差还是大于1,原因是:左边图,8和9 按照二叉排序树的要求,挂在了它的右边,经过调整后,发现都比右边图的10结点小,于是挂在了它的左边,等于没有解决问题!

解决方案:双旋转

1)先得出整体是左边高还是右边高,来进行左旋转或者右旋转;但此时我们不要再先从根结点出发了进行调整了,而是 从 根结点的左右子树高度一侧,将左子树或者右子树当做根结点的一棵新树,先进行一次左旋转或者右旋转;调整后,得到的树,再判断左右子树的高度,此时再从根结点出发,进行左旋转或者右旋转!
2)可以看出,我们开始单一讲的进行左旋转或者右旋转,进行一次,根结点是会发生变化的;而要进行双旋转的树,第一次变化时,根结点没有发生变化,第二次时,才发生变化
3)如下图的调整步骤↓↓↓ 开始的根结点为10 ,对这棵树的根结点的左子树7出发进行一次调整后,整棵树的根结点还是10,只是他的左子树根结点由原来的7变成了8;最后对第一次变化完的数,从根结点出发,根结点发生变化,由原来的10变成了8 !
在这里插入图片描述

2.3.1 LR型—双旋转—先左旋后右旋

\quad \quad 当它的左子树的右子树的高度 大于 它的左子树的左子树的高度(LR型)且左子树高度大于右子树高度(差值>1)时,对二叉树先左旋,再右旋。

(1) 围绕根节点的左孩子进行RR旋转即左旋转。
(2) 围绕根节点进行LL旋转即右旋转。

【图解过程】
在这里插入图片描述

2.3.2 RL型—双旋转—先右旋后左旋

\quad \quad 当它的右子树的左子树的高度 大于 它的右子树的右子树的高度(RL型)且右子树高度大于左子树高度(差值>1)时,对二叉树先右旋,再左旋。

(1) 围绕根节点的右孩子进行LL旋转即右旋转。
(2) 围绕根节点进行RR旋转即左旋转。

【图解过程】

在这里插入图片描述
注:从这四种旋转可以看出,首先我们应该判断树是LL、RR、LR、RL?然后对于LL就进行右旋转一次,对于RR就进行左旋转一次,对于LR先进行内旋转(也就是左旋转)再进行外旋转(也就是右旋转),对于RL同样是先进行内旋转(也就是右旋转),然后进行外旋转(也就是左旋转)然后就把树转化成了平衡树。

3、构造平衡二叉树

\quad \quad 构造平衡二叉树跟构造搜索二叉树差不多,只不过在每插入一个结点时,需要判断是否失衡,如果失衡,则需要调整

3.1 例子

\quad \quad 输入关键字序列(16,3,7,11,9,26,18,14,15),给出构造一棵AVL树的步骤。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • AVL树构造完毕

3.2python实现AVL树

1、判断是否平衡?—计算 二叉树 左右子树以及树 的高度

\quad \quad 条件:子树与子树的高度之差的绝对值小于等于1
\quad \quad 需求:需要计算左右子树以及树的高度

class TreeNode(object):
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None


class AVLTree(object):
    def __init__(self):
        self.root = None

    # 返回左子树的高度
    def left_height(self, node): # 开始传入根结点,后面传入每颗子树的根结点
        if node is None:
            return 0
        return self.tree_height(node.left)

    # 返回右子树的高度
    def right_height(self, node): # 开始传入根结点,后面传入每颗子树的根结点
        if node is None:
            return 0
        return self.tree_height(node.right)

    # 返回以该结点为根结点的树的高度
    def tree_height(self, node): 
        if node is None:
            return 0
        return max(self.tree_height(node.left), self.tree_height(node.right)) + 1 # 注意加1

    # 添加结点
    def add(self, val):
        node = TreeNode(val)
        if self.root is None:
            self.root = node
            return
        queue = [self.root]
        while queue:
            temp_node = queue.pop(0)
            # 判断传入结点的值和当前子树结点的值关系
            if node.val < temp_node.val:
                if temp_node.left is None:
                    temp_node.left = node
                    return
                else:
                    queue.append(temp_node.left)
            if node.val >= temp_node.val:
                if temp_node.right is None:
                    temp_node.right = node
                    return
                else:
                    queue.append(temp_node.right)
    '''
    也可以用递归实现添加结点,记得传入根结点
    def add(self, root, val):
        node = TreeNode(val)
        if root is None:
            self.root = node
            return
        if val < root.val:
            if not root.left:
                root.left = node
                return
            else:
                self.add(root.left, val)
        else:
            if not root.right:
                root.right = node
                return
            else:
                self.add(root.right, val)
    '''        
	# 中序遍历测试
    def in_order(self, node):
        if node is None:
            return
        self.in_order(node.left)
        print(node.val, end=" ")
        self.in_order(node.right)


if __name__ == '__main__':
    a = AVLTree()
    node_array = [4, 3, 6, 5, 7, 8]
    for item in node_array:
        # a.add(a.root, item) 递归添加结点时用
        a.add(item)
    print("中序遍历结果为:")
    a.in_order(a.root) 
    print()
    print("树的高度为:", a.tree_height(a.root)) 
    print("左子树高度为:", a.left_height(a.root)) 
    print("右子树高度为:", a.right_height(a.root)) 
    
''' 输出结果为:
中序遍历结果为:
3 4 5 6 7 8 
树的高度为: 4
左子树高度为: 1
右子树高度为: 3
'''

2、定义左旋转、右旋转并实现平衡二叉树

import copy
class TreeNode(object):
    def __init__(self, val):
        self.val = val
        self.left = None
        self.right = None


class AVLTree(object):
    def __init__(self):
        self.root = None

    # 返回左子树的高度
    def left_height(self, node):  # 开始传入根结点,后面传入每颗子树的根结点
        if node is None:
            return 0
        return self.tree_height(node.left)

    # 返回右子树的高度
    def right_height(self, node):  # 开始传入根结点,后面传入每颗子树的根结点
        if node is None:
            return 0
        return self.tree_height(node.right)

    # 返回以该结点为根结点的树的高度
    def tree_height(self, node):
        if node is None:
            return 0
        return max(self.tree_height(node.left), self.tree_height(node.right)) + 1

    # 进行左旋转
    def left_rotate(self, node):
        if node is None:
            return
        # 创建新的结点,以当前根结点的值
        new_node = copy.deepcopy(node)
        # 把新结点的左子树设为当前结点的左子树
        new_node.left = node.left
        # 把新结点的右子树设为当前结点的右子树的左子树
        new_node.right = node.right.left
        # 把当前结点的值替换成它的右子结点的值
        node.val = node.right.val
        # 把当前结点的右子树设置成当前结点的右子树的右子树
        node.right = node.right.right
        # 把当前结点的左子结点设置成(指向)新的结点
        node.left = new_node

    # 进行右旋转
    def right_rotate(self, node):
        if node is None:
            return
        # 创建新的结点,以当前根结点的值
        new_node = copy.deepcopy(node)
        # 把新结点的右子树设为当前结点的右子树
        new_node.right = node.right
        # 把新结点的左子树设为当前结点的左子树的右子树
        new_node.left = node.left.right
        # 把当前结点的值替换成它的左子结点的值
        node.val = node.left.val
        # 把当前结点的左子树设置成当前结点的左子树的左子树
        node.left = node.left.left
        # 把当前结点的右子结点设置成(指向)新的结点
        node.right = new_node

    # 添加结点
    def add(self, val):
        node = TreeNode(val)
        if self.root is None:
            self.root = node
            return
        queue = [self.root]
        while queue:
            temp_node = queue.pop(0)
            # 判断传入结点的值和当前子树结点的值关系
            if node.val < temp_node.val:
                if temp_node.left is None:
                    temp_node.left = node
                    return
                else:
                    queue.append(temp_node.left)
            if node.val >= temp_node.val:
                if temp_node.right is None:
                    temp_node.right = node
                    return
                else:
                    queue.append(temp_node.right)

    '''
    # 递归添加结点
    def add(self, root, val):
        node = TreeNode(val)
        if root is None:
            self.root = node
            return
        if val < root.val:
            if not root.left:
                root.left = node
                return
            else:
                self.add(root.left, val)
        else:
            if not root.right:
                root.right = node
                return
            else:
                self.add(root.right, val)
    '''

    def jude_node(self, node):  # 判断二叉排序树是否需要调整(是否达到平衡)
        if self.right_height(node) - self.left_height(node) > 1:  # 右子树高于左子树
            # 如果它的右子树的左子树的高度 大于 它的右子树的右子树高度
            if node.right and self.left_height(node.right) > self.right_height(node.right):
                # 先对当前结点的右子结点(右子树)进行-> 右旋转
                self.right_rotate(node.right)
                # 再对当前结点进行左旋转
                self.left_rotate(self.root)
            else:
                # 直接进行左旋转
                self.left_rotate(self.root)
            return # 注意这个return 因为判断为一种情况,就要总结判断
        if self.left_height(node) - self.right_height(node) > 1:  # 左子树高于右子树
            # 如果它的左子树的右子树的高度 大于 它的左子树的左子树的高度
            if node.left and self.right_height(node.left) > self.left_height(node.left):
                # 先对当前结点的左子结点(左子树)进行-> 左旋转
                self.left_rotate(node.left)
                # 再对当前结点进行右旋转
                self.right_rotate(self.root)
            else:
                # 直接进行右旋转
                self.right_rotate(self.root)

    # 中序遍历测试
    def in_order(self, node):
        if node is None:
            return
        self.in_order(node.left)
        print(node.val, end=" ")
        self.in_order(node.right)

    # 前序遍历测试(主要看根结点的变化)
    def pre_order(self, node):
        if node is None:
            return
        print(node.val, end=" ")
        self.pre_order(node.left)
        self.pre_order(node.right)


if __name__ == '__main__':
    a = AVLTree()
    # 测试左旋转结点值数组
    # node_array = [4, 3, 6, 5, 7, 8]
    # 测试右旋转结点值数组
    # node_array = [10, 12, 8, 9, 7, 6]
    # 测试双旋转结点数组
    node_array = [10, 11, 7, 6, 8, 9]
    for item in node_array:
        # a.add(a.root, item)
        a.add(item)
    a.jude_node(a.root)
    print("中序遍历结果为:")
    a.in_order(a.root)
    print()
    print("旋转后,前序遍历结果为:")
    a.pre_order(a.root)
    print()
    print("树的高度为:", a.tree_height(a.root))
    print("左子树高度为:", a.left_height(a.root))
    print("右子树高度为:", a.right_height(a.root))

''' 输出结果
中序遍历结果为:
6 7 8 9 10 11 
旋转后,前序遍历结果为:
8 7 6 10 9 11 
树的高度为: 3
左子树高度为: 2
右子树高度为: 2
'''

参考资料:
1、https://blog.csdn.net/qq_25343557/article/details/89110319
2、相关视频
3、https://blog.csdn.net/qq_21993785/article/details/80576642

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值