数据结构与算法--平衡二叉树/AVL树 一步一步带你用Python完整实现平衡二叉树/AVL树 Python实现平衡二叉树/AVL树的调整 AVL树结点/节点的调整

基本概述

  • 为什么要有平衡二叉树?
    在这里插入图片描述

  • 平衡二叉树 定义
    在这里插入图片描述

  • 注意:平衡二叉树一定是要再二叉排序树的基础上变化的,所以结点的值应该满足二叉排序树的规则

平衡二叉树 左旋转

  • 如下图数组[4,3,6,5,7,8] 构建的二叉排序树,它的左子树高度为1,右子树高度为3,不满足平衡二叉树的定义;因为右子树高度大于左子树高度,所以我们应该进行单旋转–左旋转 操作;左旋转的步骤如下↓↓↓
    在这里插入图片描述

  • 按步骤 完成 左旋转后↓↓↓
    在这里插入图片描述

  • 完成 左旋转步骤后,结点6没有任何结点指向它,即断开链接,它不会被访问到

  • 很显然要进行左旋转或者右旋转 都是通过左右子树的高度来决定的,所以,要实现的第一步是如何计算出左右子树的各自高度和整棵树的高度!

计算 二叉树 左右子树以及树 的高度

  • 我们可以用递归的方式来求左右子树的各自高度和整棵树的高度!
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
'''
  • 接下来,我们来完成树的左旋转,因为是分步完成,我们测试时,让右子树高于左子树
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 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 jude_node(self, node):  # 判断二叉树是否需要调整
        if abs(self.right_height(node) - self.left_height(node)) > 1: # 假设的是>1,而且右子树高于左子树
            self.left_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]
    for item in node_array:
        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))


'''输出结果
中序遍历结果为:
3 4 5 6 7 8 
左旋转后,前序遍历结果为:
6 4 3 5 7 8 
树的高度为: 3
左子树高度为: 2
右子树高度为: 2
'''
  • 分析:根据之前未调整的二叉排序树,我们知道:树的高度,左子树高度,右子树高度分别为>>>4,1,3 (右子树更高),调整后分别为:3,2,2 ,再来看遍历测试,因为调整后也要满足是二叉排序树,所以中序遍历的结果还是和未调整前一样为:3 4 5 6 7 8 ,于是我们增加一个前序遍历来看,因为调整后,根结点发生了变化,所以结果会不同,于是得到前序遍历结果为:6 4 3 5 7 8 ,那到底对不对呢?来看下这张我们调整规则的示意图↓↓↓ 红色部分为调整后的二叉排序树

在这里插入图片描述

  • 恭喜我们做对了,完成了一种左旋转调整情况!

平衡二叉树 右旋转

  • 为分步解释可能出现的情况,所以每种情况输入的结点是不一样的,这次为了说明需要右旋转的情况,我们选择结点数组为:[10,12,8,9,7,6],操作步骤和左旋转非常类似
    在这里插入图片描述
    在这里插入图片描述
  • 总体代码都是一样的,只是方向调整一下就可以了
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, root):  # 判断二叉树是否需要调整
        if self.right_height(root) - self.left_height(root) > 1:  # 右子树高于左子树
            self.left_rotate(self.root)  # 直接左旋转
        if self.left_height(root) - self.right_height(root) > 1:  # 左子树高于右子树
            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]
    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 12 
右旋转后,前序遍历结果为:
8 7 6 10 9 12 
树的高度为: 3
左子树高度为: 2
右子树高度为: 2
'''

平衡二叉树 双旋转

  • 发现问题:加入我们把测试数组换成: [10, 11, 7, 6, 8, 9] ,因为上面已经实现了左旋转和右旋转,按理来说,无论哪边高,都会调用各自的调整,结果测试结果为:
    在这里插入图片描述
    发现树的高度未能满足平衡二叉树的要求,这是什么原因呢?
  • 分析:如下图↓↓↓
    在这里插入图片描述
    (1)左图满足右旋转,于是我们进行右旋转,最终7变成了新的根结点,8和9挂到哦了10结点的左边,发现高度差还是大于1,原因是:左边图,8和9 按照二叉排序树的要求,挂在了它的右边,经过调整后,发现都比右边图的10结点小,于是挂在了它的左边,等于没有解决问题!
    (2)我们再来看,之前我们处理左旋转和右旋转时,开始的结点图:
    在这里插入图片描述
    在这里插入图片描述
    (3)仔细观察上面两张我们进行左旋转,右旋转就成功时的图,发现:根结点的左子结点或者右子结点,它们的各种的左或者右子结点数为1;回到上面,这次我们看测试数组换成: [10, 11, 7, 6, 8, 9] 的图↓↓↓ 它的结点数>1 (即:它的左子树的右子树的高度 大于 它的左子树的左子树的高度 下图是 左子树的右子树的高度 2 >左子树的左子树高度1 ),所以我们不能简单的进行单次的左旋转或者右旋转
    在这里插入图片描述
  • 解法方案:双旋转
    (1)先得出整体是左边高还是右边高,来进行左旋转或者右旋转;但此时我们不要再先从根结点出发了进行调整了,而是 从 根结点的左右子树高度一侧,将左子树或者右子树当做根结点的一棵新树,先进行一次左旋转或者右旋转;调整后,得到的树,再判断左右子树的高度,此时再从根结点出发,进行左旋转或者右旋转!
    (2)可以看出,我们开始单一讲的进行左旋转或者右旋转,进行一次,根结点是会发生变化的;而要进行双旋转的树,第一次变化时,根结点没有发生变化,第二次时,才发生变化
    (3)如下图的调整步骤↓↓↓ 开始的根结点为10 ,对这棵树的根结点的左子树7出发进行一次调整后,整棵树的根结点还是10,只是他的左子树根结点由原来的7变成了8;最后对第一次变化完的数,从根结点出发,根结点发生变化,由原来的10变成了8 !
    在这里插入图片描述
  • 具体操作,只需在是否进行调整的代码加上,分别加上判断即可:
'''上面代码省略'''

    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)

if __name__ == '__main__':
    a = AVLTree()
    # 测试双旋转结点数组
    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
'''

恭喜我们做对了~~

Python完整实现平衡二叉树的所有功能

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
'''
发布了146 篇原创文章 · 获赞 37 · 访问量 7854

猜你喜欢

转载自blog.csdn.net/storyfull/article/details/103833760