AVL树
1. AVL树的概念
AVL树又称为高度搜索树,它是一个特殊的二叉搜索树,当元素接近于有序的时候,二叉树也会变成一个单链树,所以AVL树就是平衡二叉搜索树,当插入一个节点,树的任意一个左右子树的高度差都<=1,称为AVL树
上图左边的二叉树的每个节点的左右子树的最大高度差都不超过1,而右边的二叉树的节点7的左右子树的高度差为2,不符合AVL树的定义
2. 旋转算法:左左右,右右左,左右左右,右左右左
1. 由于对二叉树的K的右孩子的右子树插入导致不平衡:左旋
2. 由于对二叉树的K的左孩子的左子树插入导致不平衡:右旋
3. 由于二叉树对K的右孩子的左子树插入导致不平衡:右旋→左旋
4. 由于二叉树对K的左孩子的右子树插入导致不平衡:左旋→右旋
3. AVL数的插入实现
from 二叉搜索树 import * # 继承 class AVLNode1(BiTreeNode): def __init__(self, item): BiTreeNode.__init__(self, item) self.bf = 0 # AVL树,继承二叉搜索树,代码在二叉树章 class AVLtree(BiSelectTree): def __init__(self, li=None): BiSelectTree.__init__(self, li) class AVLNode: def __init__(self, item): self.item = item self.lchild = None # 左孩子 self.rchild = None # 右孩子 self.parent = None # 父节点 self.bf = 0 # balance factor平衡因素,左右子树高度差 class AVLtree: def __init__(self, li=None): self.root = None if li: for val in li: self.insert(val) # 要配合图理解 # 左旋函数,即插入右子树的右孩子,p不平衡节点,c是p的右子树 def rotate_left(self, p, c): s2 = c.lchild # 存c的左孩子,即没有变化的节点 p.rchild = s2 # 旋转之后p是c的左孩子,p的右孩子等于c的左孩子 # 当c为叶子节点,没有左右孩子,则不存在s2,所以需要判断一下 if s2: s2.parent = p c.lchild = p # 旋转之后,c的左孩子变成了他的父节点 p.parent = c # p的父节点就成了他的子节点 p.bf = 0 # 因为s1和s2是相同的深度,如果深度不同,那原来的树不符合AVL树 c.bf = 0 # 因为s2和s3相差了一个深度,当p加入左节点后,这两个深度相同 return c # 返回根节点,用来和旋转前的父节点连接 # 右旋函数,即插入左子树的左孩子,和左旋函数一样的原理 def rotate_right(self, p, c): s2 = c.rchild # 记录左子树的右孩子 p.lchild = s2 # 旋转之后,p和c的位置交换,p的左孩子等于原来的c的右孩子 # 非叶子节点判断 if s2: s2.parent = p # 将p和c连接起来 c.rchild = p p.parnet = c # 和左旋一样的道理,如果是平衡二叉树,必须符合左右子树深度差小于1,插入之后深度相同 p.bf = 0 c.bf = 0 return c # 右旋-左旋函数:插入右子树的左孩子,p是c的父节点,c是p的左孩子 def rotate_right_left(self, p, c): g = c.lchild # 分别存一下g的两个孩子,s2比g小比p大,所以旋转之后会在平衡节点的g的左子树的p的右子树 s2 = g.lchild s3 = g.rchild # 判断s2是否存在 if s2: s2.parent = p # p.rchild = s2 # 这个可以不写,因为它必定是p的右孩子 g.lchild = p p.parent = g # 判断s3是否存在 if s3: s3.parent = c # c.lchild = s3 # 必定是c的左孩子 g.rchild = c c.parent = g # bf等于左子树深度减去右子树深度 if g.bf > 0: # 即左子树比右子树大1层,设左孩子有,右孩子没有 p.bf = 0 c.bf = -1 elif g.bf < 0: # 即右子树比左子树大1层,设想右孩子有,左孩子没有 p.bf = 1 c.bf = 0 else: # 等于0,左右孩子都存在 p.bf = 0 c.bf = 0 return g # 左旋-右旋函数:插入左子树的右孩子, p是c的父节点,c是p的左孩子 def rotate_left_right(self, p, c): g = c.rchild # 分别存一下g的两个孩子,32比g大比p小,所以旋转之后会在平衡节点的g的右子树的p的左子树 s2 = g.lchild s3 = g.rchild # 判断s2是否存在 if s2: s2.parent = c c.rchild = s2 # 这个可以不写,因为它必定是p的右孩子 g.lchild = c c.parent = g # 判断s3是否存在 if s3: s3.parent = p p.lchild = s3 # 必定是c的左孩子 g.rchild = p p.parent = g # 更新bf if g.bf > 0: # 左孩子有,右孩子没有 c.bf = 0 p.bg = -1 elif g.bf < 0: # 右孩子有,左孩子没有 c.bf = 1 p.bf = 0 else: c.bf = 0 p.bf = 0 return g # 插入节点 def insert(self, val): pointer = self.root # 指针指向节点 if not pointer: self.root = AVLNode(val) # 空树直接插入节点 return while True: # 小于指针节点,则插入左边 if val < pointer.item: if pointer.lchild: # 如果左孩子存在,则指向左孩子 pointer = pointer.lchild else: # 左孩子不存在,则直接插入,并指定父节点 pointer.lchild = AVLNode(val) pointer.lchild.parent = pointer node = pointer.lchild # 存储插入节点,需要移动 break # 大于指针节点,则插入右边,逻辑和小于一致 elif val > pointer.item: if pointer.rchild: pointer = pointer.rchild else: pointer.rchild = AVLNode(val) pointer.rchild.parent = pointer node = pointer.rchild break else: return # 更新bf while node.parent: # 说明不是根节点 if node.parent.lchild == node: # 说明插入了左子树,左边深度+1 # 情况1:大于0,说明node父节点的左子树比右子树大,插入左子树更新之后node.parent的bf=2,需要进行旋转 if node.parent.bf > 0: g_parent = node.parent.parent # 原来的平衡节点的父节点 old_g = node.parent # 旋转之前的平衡节点,即比较的那个顶点 if node.bf < 0: # 说明插入node的右孩子,即左子树的右孩子 # n为旋转之后的平衡点 n = self.rotate_left_right(node.parent, node) # 这里bf大于等于0,大于0指叶子结点,插入node的左子树后,node的左边比右边多1层,等于0是,原来右比左大1,插入后左右子树相同 else: n = self.rotate_right(node.parent, node) # 情况2:,小于0,说明右子树比左子树大,插入左边更新之后左右子树相同,即node.parent的bf=0,则二叉搜索树不需进行旋转 elif node.parent.bf < 0: node.parent.bf = 0 break # 情况3,等于0,说明左右子树原来相等,插入左子树后, node.parent的bf=1,符合AVL树,不需要进行转换 else: node.parent.bf = 1 node = node.parent # 有插入节点往根节点检查bf值,循环执行 continue # 跳过后续执行前面的循环 # # 说明插入了右子树,右边深度+1 else: # 情况1:小于0,说明node父节点的右子树比左子树大,插入右子树更新之后node.parent的bf=2,需要进行旋转 if node.parent.bf < 0: g_parent = node.parent.parent # 原来的平衡节点的父节点 old_g = node.parent # 旋转之前的平衡节点,即比较的那个顶点 # 大于0即1,就是左子树比右子树大,即插入了左边,执行选择,口诀:右左右左 if node.bf > 0: n = self.rotate_right_left(node.parent, node) # 这里bf小于等于0,插入之后小于0,则原来为叶子结点,插入node的右子树后,node的右边比左边多1层,等于0是,原来左比右大1,插入后左右子树相同 else: n = self.rotate_left(node.parent, node) # 口诀:右右左 # # 情况2:,大于0,说明左子树比右子树大,插入右子树更新之后左右子树相同,即node.parent的bf=0,则二叉搜索树不需进行旋转 elif node.parent.bf > 0: node.parent.bf = 0 break # 情况3,等于0,说明左右子树原来相等,插入右子树后, node.parent的bf=1,符合AVL树,不需要进行转换 else: node.parent.bf = -1 node = node.parent # 往根节点进行查找bf是否存在大于1的情况 continue # 连接旋转后的子树 n.parent = g_parent # g是旋转之前的平衡点的父节点,n是旋转之后的平衡点 if g_parent: if old_g == g_parent.lchild: # x为旋转之前的平衡点,是它父节点的左孩子,则n为左孩子,否则为右孩子 g_parent.lchild = n else: g_parent.rchild = n break # g不存在说明旋转之后的平衡点就是整棵树的根节点 else: self.root = n break # 前序遍历 def pro_order(self, root): if root: print(root.item,end=',') self.pro_order(root.lchild) self.pro_order(root.rchild) # 中序遍历 def cen_order(self, root): if root: self.cen_order(root.lchild) print(root.item, end=',') self.cen_order(root.rchild) # 后续遍历 def later_order(self, root): if root: self.later_order(root.lchild) # 先找左边的子节点 self.later_order(root.rchild) # 在找优点的子节点 print(root.item, end=',') tree = AVLtree([9,8,7,6,5,4,3,2,1]) tree.pro_order(tree.root) # 继承方法,前序查询 6,4,2,1,3,5,8,7,9 print("") tree.cen_order(tree.root) # 继承方法,中序查询 1,2,3,4,5,6,7,8,9 print("") tree.later_order(tree.root) # 继承方法,后序查询 1,3,2,5,4,7,9,8,6
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现