基本概述
-
为什么要有平衡二叉树?
-
平衡二叉树 定义
-
注意:平衡二叉树一定是要再二叉排序树的基础上变化的,所以结点的值应该满足二叉排序树的规则
平衡二叉树 左旋转
-
如下图数组[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
'''