这一节关于 AVL 平衡二叉搜索树的题目,刷了不少题,关于平衡树的题目确实很少,做来做去,就先看下面这两道小儿科的题目吧。
110. Balanced Binary Tree
Given a binary tree, determine if it is height-balanced.
For this problem, a height-balanced binary tree is defined as:
a binary tree in which the depth of the two subtrees of every node never differ by more than 1.
题目解析:
简单来说,就是检查每个结点左右子树的深度,之差不大于1. 这里构造了一个检查的函数,递归求树深,该结点的深度为左右子树深度的最大值加1, 另一方面,函数中判断左右子树深度的差值,并返回一个布尔值,表示其是否平衡。为了速度快, 当子树已经为不平衡时,会直接向上递归回去结果。代码如下, 速度击败95%.
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
def check(root):
if not root:
return 0, True
d1, f1 = check(root.left)
if not f1:
return 0, False
d2, f2 = check(root.right)
if not f2:
return 0, False
if abs(d1-d2) < 2 and f1 and f2:
return max(d1, d2) + 1, True
else:
return 0, False
d1, f1 = check(root)
return f1
108. Convert Sorted Array to Binary Search Tree
Given an array where elements are sorted in ascending order, convert it to a height balanced BST.
For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1.
题目解析:
BST中序遍历 就是一个递增的序列, 现在要求你逆操作,构造一棵平衡的二叉搜索树。如下代码借鉴了二分查找,不断二分,mid指向元素作为根结点,两侧依次是其左右子树,当左右子树只有一个元素时停止。代码结合了递归思想,二分思想,构造了一个中间函数,速度极佳, 打败了99%。
class Solution:
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
head = None
if not nums:
return head
def helper(lt, rt):
if lt == rt:
return TreeNode(nums[lt])
mid = (lt + rt) // 2
p = TreeNode(nums[mid])
if mid > lt:
p.left = helper(lt, mid-1)
if mid < rt:
p.right = helper(mid+1, rt)
return p
head = helper(0, len(nums)-1)
return head
上面两道题都是BST相关基本应用+平衡树的概念,但是熟练应用二叉树的常用解题思路,如遍历,递归, 栈,队列等,还需多思考。
前方高能。。。
LeetCode中并没有对AVL的增删操作的考察(反正我没看到),这应该是AVL的基本功,与任何场景都无关,下面我们就认真撕一下这个问题:实现在AVL平衡二叉树中结点的插入。
大家也可参考相关博客,本人借鉴了以下博客内容。
首先这是树的结构定义:
class AVLTreeNode(object):
def __init__(self, key):
self.key = key
self.p = None
self.left = None
self.right = None
self.height = 0
def __str__(self): # 有助于我们后面查看
return "{ %d : left %s ; right %s }" % (self.key, self.left, self.right)
下面我们看一下核心的旋转代码,先看右旋:
def right_rotate(root, node): # 右旋, 用于LL型失衡
# 先将 node 和 node_left 之间及其左右节点赋值 (node_left.left node.right 保持不变)
node_left = node.left
node.left = node_left.right
node_left.right = node
# 然后几个相关结点的父子关系梳理
if not node.p:
root = node_left
node_left.p = None
elif node == node.p.left:
node.p.left = node_left
node_left.p = node.p
elif node == node.p.right:
node.p.right = node_left
node_left.p = node.p
node.p = node_left
# 调整树高, 由node向上层更新树高
while node:
node.height = max(get_height(node.left), get_height(node.right)) + 1
node = node.p
return root
配合之前的动图来看:
再看左旋,
def left_rotate(root, node): # 适用于RR型失衡
# 还是一样的三个步骤
node_right = node.right
node.right = node_right.left
node_right.left = node
if not node.p:
root = node_right
node_right.p = None
elif node == node.p.left:
node.p.left = node_right
node_right.p = node.p
elif node == node.p.right:
node.p.right = node_right
node_right.p = node.p
node.p = node_right
while node:
node.height = max(get_height(node.left), get_height(node.right)) + 1
node = node.p
return root
因此,LR型失衡先左旋再右旋,RL型失衡先右旋再左旋,代码如下:
def left_right_rotate(root, node): # LR型失衡
# 先对左子树左旋, 转为LL型失衡
root = left_rotate(root, node.left)
root = right_rotate(root, node)
return root
def right_left_rotate(root, node): # RL型失衡
# 先对右子树右旋,转为RR型失衡
root = right_rotate(root, node.right)
root = left_rotate(root, node)
return root
上面几个函数实现了AVL中失衡类型确定后的调整,下面我们看一下插入结点的全流程,需要注意的是,调整失衡时,当根结点失衡,要特殊对待,此时树的根结点会调整,返回新的根结点。下面代码参考其他博客,并调整了一定的可读性。
def get_height(node):
return node.height if node else -1
def insert(root, val): # 入参:树根及插入值, 返回:新的树根(因为可能会变)
node = AVLTreeNode(val)
# 空树 把 root 赋值即可, 无需其他步骤
if not root:
return node
temp = root
temp_node = None
# 找到要插入的父节点(temp_node) 若值已存在,则抛出错误
while temp:
temp_node = temp
if node.key < temp.key:
temp = temp.left
elif node.key > temp.key:
temp = temp.right
else:
raise KeyError("Error!")
# 父子相认
if node.key < temp_node.key:
temp_node.left = node
elif node.key > temp_node.key:
temp_node.right = node
node.p = temp_node
# 从父结点 temp_node 开始向上更新每个结点的高度
temp_p = temp_node
while temp_p:
temp_p.height = max(get_height(temp_p.left), get_height(temp_p.right)) + 1
temp_p = temp_p.p
# 最后从插入结点 node本身开始,调整平衡
root = balance(root, node)
return root
def balance(root, node):
while node:
# 调整思路很简单,从node向上层,找到第一个失衡的结点,判断失衡类型,进行相应调整
if get_height(node.left) - get_height(node.right) == 2:
if node.left.left:
root = right_rotate(root, node)
else:
root = left_right_rotate(root, node)
return root
elif get_height(node.right) - get_height(node.left) == 2:
if node.right.right:
root = left_rotate(root, node)
else:
root = right_left_rotate(root, node)
return root
node = node.p
return root
至于结点的删除,懒得再撕了,需要的话自行掰吃。
在完成上述代码后,我们如何检验一下呢?把上面所有函数和类都汇总,运行下面main方法。构造的合适的插入顺序,插入过程会遇到四次失衡。
if __name__ == '__main__':
number_list = (7, 4, 1, # LL
8, 5, 12, # RR
9, # RL
3, 2 # RL
)
root = None
for number in number_list:
root = insert(root, number)
print(root)
我们来看一下最终结果, 自己手动插入以上结点,看看代码运行是否正确,以及自己理解的是否有偏差。
"""
{ 7 : left None ; right None }
{ 7 : left { 4 : left None ; right None } ; right None }
{ 4 : left { 1 : left None ; right None } ; right { 7 : left None ; right None } }
{ 4 : left { 1 : left None ; right None } ; right { 7 : left None ; right { 8 : left None ; right None } } }
{ 4 : left { 1 : left None ; right None } ; right { 7 : left { 5 : left None ; right None } ; right { 8 : left None ; right None } } }
{ 7 : left { 4 : left { 1 : left None ; right None } ; right { 5 : left None ; right None } } ; right { 8 : left None ; right { 12 : left None ; right None } } }
{ 7 : left { 4 : left { 1 : left None ; right None } ; right { 5 : left None ; right None } } ; right { 9 : left { 8 : left None ; right None } ; right { 12 : left None ; right None } } }
{ 7 : left { 4 : left { 1 : left None ; right { 3 : left None ; right None } } ; right { 5 : left None ; right None } } ; right { 9 : left { 8 : left None ; right None } ; right { 12 : left None ; right None } } }
{ 7 : left { 4 : left { 2 : left { 1 : left None ; right None } ; right { 3 : left None ; right None } } ; right { 5 : left None ; right None } } ; right { 9 : left { 8 : left None ; right None } ; right { 12 : left None ; right None } } }
"""
好,这一节艰难的客服过去了。。。