#!/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 = valself.left = leftself.right = rightself.height = height# 二叉平衡树的基本操作
class AVL_ops:# 计算以当前节点为根节点的树的高度def getTreeHeight(self,root):if root == None:return 0return root.height# 更新以当前节点为根节点的树的高度 = max(左子树节点高度,右子树节点高度) + 1def 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 = tempreturn 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 = tempreturn 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或者LRif 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或者RLelse: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 Noneroot = TreeNode(nums[0])for num in nums[1:]:root = self.AVL_insert(root,num)return root# 层序遍历AVLdef AVL_layer(self,root):if root is None:returnq = [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 = newQprint("\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,
注意和二叉查找树的细节区别,细节区别见本文代码前部分《遇到的问题》