【Python数据结构】——二叉平衡树AVL(查找、构建、删除、插入、打印、遍历)

#!/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,

注意和二叉查找树的细节区别,细节区别见本文代码前部分《遇到的问题》

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有情怀的机械男

你的鼓励将是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值