#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/7/28 20:57
# @Author : @linlianqin
# @Site :
# @File : 二叉平衡树专题(创建、插入、查找).py
# @Software: PyCharm
# @description:
'''
二叉平衡树的特点:在二叉查找树的基础上对每一个节点的左右子树的高度进行了规定——每一个节点的左右子树的高度差不超过1
1)任何一个节点都有:左子树节点值 <= 节点值 < 右子树节点值
2)任何一个节点的左子树高度和右子树高度之差不超过1
两个概念:
1)当前节点的高度 = max(左子树节点高度,右子树节点高度) + 1
2)平衡因子 = 左子树节点高度 - 右子树节点高度
调式代码过程中出现的问题:
1)高度值混淆:高度值规定——空树高度为0,默认节点高度值为1,叶节点的高度值默认为1;
2)注意插入方法和二叉查找树的插入方法的区别:
BST的插入过程中,头节点是不会发生改变的,因为只需要满足左<=中<右即可;
AVL的插入过程中,由于要维持树的平衡,同时要满足BST的性质,因此再插入过程中,各个子树的根节点是会发生改变的,因此插入结束后,需要将最新的头节点返回,以便后续的遍历
3)在更新节点高度值的时候,设计更新节点高度值函数的时候没有更新节点的高度值,而是直接返回了,
导致节点高度值出现错误
错误代码:return max(self.getTreeHeight(root.left),self.getTreeHeight(root.right))+1
正确代码:root.height = max(self.getTreeHeight(root.left),self.getTreeHeight(root.right))+1
4)插入元素后,在进行左旋\右旋的时候,没有接受返回来的调整平衡后的子树根节点,导致最后遍历AVL树的时候元素不完整
错误代码:self.AVL_L(root.left)
正确代码:root.left = self.AVL_L(root.left)
'''
# 定义二叉平衡树的节点类,比普通的二叉树节点类多了一个高度属性
class TreeNode:
def __init__(self,val,left=None,right=None,height=1):
self.val = val
self.left = left
self.right = right
self.height = height
# 二叉平衡树的基本操作
class AVL_ops:
# 计算以当前节点为根节点的树的高度
def getTreeHeight(self,root):
if root == None:
return 0
return root.height
# 更新以当前节点为根节点的树的高度 = max(左子树节点高度,右子树节点高度) + 1
def updateTreeHeight(self,root):
## 这里不需要判断左右节点是否为空,是因为在self.getTreeHeight函数中已经将空节点的高度设置为0了
root.height = max(self.getTreeHeight(root.left),self.getTreeHeight(root.right))+1
# 计算当前节点的平衡因子 = (左子树节点高度 - 右子树节点高度)
def getNodeBalanceFactor(self,root):
return self.getTreeHeight(root.left)- self.getTreeHeight(root.right)
# 在二叉平衡树中的查找目标值—-因为AVL实际上是BST因此查找也是一样的
def AVL_search(self,root,target):
# 访问到了空树的时候,说明目标值不存在
if root is None:
return False
# 相等说明目标值存在
if target == root.val:
return True
# 目标值在当前节点的左子树上
if target < root.val:
return self.AVL_search(root.left,target)
# 目标值在当前节点的右子树上
if target > root.val:
return self.AVL_search(root.right,target)
# 在二叉平衡树中进行插入——和BST的插入有所区别,这里是因为需要保证树的高度处于平衡状态
## 插入一个元素后,高度失去平衡的节点的平衡因子一定是2或者-2只需要将这里的失衡的节点调节就可以
## 事实证明:只需要将失衡的节点进行调节整棵树就可以达到平衡状态
## 这里分为左旋和右旋两种方式进行平衡调节,对于失衡的树有LL,LR,RR,RL四种结构
## 其中LL右旋达到平衡、LR先左节点左旋为LL再右旋、RR左旋达到平衡、RL先右节点右旋为RR然后再达到平衡
### 左旋:
def AVL_L(self,root):
# 1)临时节点存放根节点的右节点
temp = root.right
# 2)将根节点的右节点更新为temp的左节点
root.right = temp.left
# 3)将temp节点的左节点更新为根节点
temp.left = root
# 4)更新temp和root的高度值
self.updateTreeHeight(root)
self.updateTreeHeight(temp)
# 5)更新根节点
root = temp
return root
### 右旋:和左旋相反
def AVL_R(self,root):
# 1)临时节点存放根节点的左节点
temp = root.left
# 2)将根节点的左节点更新为temp的右节点
root.left = temp.right
# 3)将temp节点的右节点更新为根节点
temp.right = root
# 4)更新temp和root的高度值
self.updateTreeHeight(root)
self.updateTreeHeight(temp)
# 5)更新根节点
root = temp
return root
# 插入的话首先需要进行失衡节点处的树型判断LL\LR\RR\RL
# 步骤:
# 1)先将元素按照左小右大的性质插入到对应叶节点末尾;
# 2)再更新插入元素后的各节点高度;
# 3)根据各节点高度值计算各节点的平衡因子;
# 4)根据节点及其左右子节点的平衡因子来判断树形;
# 5)根据树形选择左旋、右旋组合来将插入后的失衡二叉树调整至平衡二叉树AVL
# 6)返回最新的根节点完成插入操作
def AVL_insert(self,root,target):
if root is None:
return TreeNode(target)
# target插入到左子树,说明左子树会出现失衡,树形可能为LL或者LR
if root.val > target:
root.left = self.AVL_insert(root.left,target)
# 更新树高
self.updateTreeHeight(root)
# 判断树形
if self.getNodeBalanceFactor(root) == 2:
# LL型,右旋
if self.getNodeBalanceFactor(root.left) == 1:
root = self.AVL_R(root)
# LR型,右旋
elif self.getNodeBalanceFactor(root.left) == -1:
root.left = self.AVL_L(root.left)
root = self.AVL_R(root)
# target插入到右子树,说明右子树会出现失衡,树形可能为RR或者RL
else:
root.right = self.AVL_insert(root.right, target)
# 更新树高
self.updateTreeHeight(root)
# 判断树形
if self.getNodeBalanceFactor(root) == -2:
# RR型,左旋
if self.getNodeBalanceFactor(root.right) == -1:
root = self.AVL_L(root)
# RL型,先对右节点右旋再对根节点左旋
elif self.getNodeBalanceFactor(root.right) == 1:
root.right = self.AVL_R(root.right)
root = self.AVL_L(root)
return root
# todo:删除元素,其实就是插入的反向操作
def AVL_delete(self,root,target):
pass
# AVL创建——其实就是一个一个插入元素
def AVL_create(self,nums):
if len(nums) == 0:
return None
root = TreeNode(nums[0])
for num in nums[1:]:
root = self.AVL_insert(root,num)
return root
# 层序遍历AVL
def AVL_layer(self,root):
if root is None:
return
q = [root]
Height = []
while q:
newQ = []
for node in q:
print(node.val,end=',')
Height.append(node.height)
if node.left is not None:
newQ.append(node.left)
if node.right is not None:
newQ.append(node.right)
q = newQ
print("\n对应节点的高度")
print(Height)
# 中序
# 遍历二叉查找数,中序遍历
def AVL_mid_scan(self,root):
if root is None:
return
# 遍历左子树
self.AVL_mid_scan(root.left)
# 遍历根节点
print(root.val, end=',')
self.AVL_mid_scan(root.right)
li = [1,2,3,4,5,6,7]
print("原始列表:",li)
# 创建
print("创建AVL")
root = AVL_ops().AVL_create(li)
# 层序遍历
print("层序遍历")
AVL_ops().AVL_layer(root)
print("中序遍历")
AVL_ops().AVL_mid_scan(root)
print("\n插入数值-1")
root = AVL_ops().AVL_insert(root,-1)
print("插入数值-1后的层序遍历")
AVL_ops().AVL_layer(root)
print("插入数值-1后的中序遍历")
AVL_ops().AVL_mid_scan(root)
print("\n插入数值5")
root = AVL_ops().AVL_insert(root,-1)
print("插入数值5后的层序遍历")
AVL_ops().AVL_layer(root)
print("插入数值5后的中序遍历")
AVL_ops().AVL_mid_scan(root)
示例结果:
原始列表: [1, 2, 3, 4, 5, 6, 7]
创建AVL
层序遍历
4,2,6,1,3,5,7,
对应节点的高度
[3, 2, 2, 1, 1, 1, 1]
中序遍历
1,2,3,4,5,6,7,
插入数值-1
插入数值-1后的层序遍历
4,2,6,1,3,5,7,-1,
对应节点的高度
[4, 3, 2, 2, 1, 1, 1, 1]
插入数值-1后的中序遍历
-1,1,2,3,4,5,6,7,
插入数值5
插入数值5后的层序遍历
4,2,6,-1,3,5,7,-1,1,
对应节点的高度
[4, 3, 2, 2, 1, 1, 1, 1, 1]
插入数值5后的中序遍历
-1,-1,1,2,3,4,5,6,7,
注意和二叉查找树的细节区别,细节区别见本文代码前部分《遇到的问题》