要了解AVL,就要先了解二叉排序树,二叉排序树(BST)的定义就是:
右子树的所有关键字均不小于根关键字的值。
- 若它的左子树不空,则左子树上的所有关键字的值均不大于根关键字的值。
- 若它的右子树不空,则右子树上的所有关键字的值均不小于根关键字的值。
- 左右子树又各是一颗二叉排序树
二叉排序树和AVL树的中序遍历都是升序(或者降序)序列
写者的二叉排序树基于里面的元素不重复。下面是二叉排序树建立插入删除。话不多说直接上代码
# -*- coding : utf-8 -*-
# @Author : zhu
# @Time : 2022/4/26 18:04
import random
class BiTreeNode(object):
# 先来定义一下树里面的节点
def __init__(self, data):
self.lchild = None # 二叉树左子树
self.rchild = None # 二叉树右子树
self.data = data # 二叉树节点的值
self.parent = None # 让结点能找到父节点 二叉树查询和插入用不到,删除能用到
class BTS(object):
# 二叉树排序树
def __init__(self, li=None): # 我们允许构造时候插入一个列表li
self.root = None # 根节点 类型是BiTreeNode类型
if li: # 有li我们就插入 插入我们用的是非递归插入
for val in li:
self.insert_no_rec(val)
def insert_no_rec(self, val):
# 非递归插入
p = self.root # p的类型是BiTreeNode类型的
if not p: # 它根都没有
self.root = BiTreeNode(val)
return
while True:
if p.data > val: # 插入的值比较小,放到p节点的左边
if p.lchild: # 我们插入时候只能插入到叶子节点,一直往下找它
p = p.lchild # 运行完我们就进行了下一次的while循环
else: # 能从左边插入 且插入的是叶子
p.lchild = BiTreeNode(val) # 左边插上
p.lchild.parent = p # 与他父亲建立关系
return
elif p.data < val: # 这个和上面同理。只不过是右边插入
if p.rchild:
p = p.rchild
else:
p.rchild = BiTreeNode(val)
p.rchild.parent = p
return
else: # 若插入的值树里面有,我们不插入它
return
def query_no_rec(self, val): # 非递归查询
p = self.root
while p: # 有p才能找
if p.data > val: # 值小去左边查
p = p.lchild
elif p.data < val: # 值大去右边
p = p.rchild
else: # 找到!
return p
return None # 退出循环了说明啥也没找到
# 删除有三种情况 node_1.删除的是叶子 node_21.删除的只有一个左孩子 node_22.删除的只有一个右孩子
# node_3.删除的有左孩子右孩子,这种情况下我们从删除的节点的右孩子一直往左找,这样我们就会得到和目标节点相差最小的点(或者从被删除的节点的左孩子一直向右找)
def __remove_node_1(self, node):
# 第一种情况 node是要被删除的,node是叶节点
if not node.parent: # 若删除是根节点,考虑一下
self.root = None
elif node.parent.lchild == node: # 如果是父节点的左孩子
node.parent.lchild = None # 父节点左孩子删去
else:
node.parent.rchild = None
def __remove_node_21(self, node):
# 第二种情况的2.1 node仅有个左孩子 node是要被删除的
if not node.parent: # 同样是根节点
self.root = node.lchild
node.lchild.parent = None # 让它父亲变为空
elif node.parent.lchild == node:
# 看到node在父亲的左子树上
node.parent.lchild = node.lchild # node父亲连node的孩子
node.lchild.parent = node.parent # node孩子连node父亲
else: # 看到node在父亲为右子树上 和上面的elif类似的思想
node.parent.rchild = node.lchild
node.lchild.parent = node.parent
def __remove_node_22(self, node):
# 第二种情况2.2 node仅有一个右孩子 那么思想和上面node_21一样 写者就省略了
if not node.parent:
self.root = node.rchild
node.rchild.parent = None
elif node.parent.lchild == node:
node.parent.lchild = node.rchild
node.rchild.parent = node.parent
else:
node.parent.rchild = node.rchild
node.rchild.parent = node.parent
def delect(self, val):
# 真正外部用到的删除函数
if not self.root: # 树为空特殊处理一下
raise ValueError("is_entry_tree")
else:
p = self.query_no_rec(val) # 返回要删除的节点
if not p: # 没找到特殊处理
raise ValueError("can_not_find_this_value")
elif not p.lchild and not p.rchild:
# 没有孩子的情况
self.__remove_node_1(p)
elif not p.rchild:
# p仅有左子树情况下
self.__remove_node_21(p)
elif not p.lchild:
# 仅有右子树
self.__remove_node_22(p)
else:
# p左右子树都有,进行第三种情况
min_node = p.rchild
while min_node.lchild:
# 有左孩子 一直走
min_node = min_node.lchild
p.data = min_node.data # 把值给p填上
# 删除min_node节点,只可能是情况1或者情况2.2
if min_node.rchild:
self.__remove_node_22(min_node)
else:
self.__remove_node_1(min_node)
def pre_order(self, root):
# 递归实现一下打印,先序遍历
if root:
print(root.data, end=",")
self.pre_order(root.lchild)
self.pre_order(root.rchild)
def in_order(self, root):
# 中序遍历
if root:
self.in_order(root.lchild)
print(root.data, end=",")
self.in_order(root.rchild)
bts = BTS([random.randint(0, 20) for i in range(10)]) # 随机10个数试一下
# bts.pre_order(bts.root)
bts.in_order(bts.root) # 我们可以通过先序和中序画出树
接下来是AVL实现,它的实现比二叉搜索树多个平衡因子balance factor(下面简写bf)。只有每个节点的bf = -1,0,1 我们才称为二叉搜索树。在本文中我们的bf=右子树的深度-左子树的深度(即右边沉bf=1)。二叉搜索树的插入有四种情况。左旋、右旋、左旋右旋、右旋左旋。下图是三种情况
对于左旋右旋我们可以用巧法记,即插入A使得不平衡,那么让A当父亲,当了父亲两个孩子都不要了 ,分别分给旁边的两颗子树,同理右旋左旋一样。对于插入还有不懂得可以询问我或者看看资料,我们注重代码的实现。
# -*- coding : utf-8 -*-
# @Author : zhu
# @Time : 2022/4/26 23:23
from test08 import BTS, BiTreeNode # test08是上一个代码.py建立的名字
# 我们二叉平衡树能用到BTS和BiTreeNode的数据结构,我们可以继承一下
class AVLNode(BiTreeNode):
def __init__(self, data):
BiTreeNode.__init__(self, data)
self.bf = 0 # 平衡因子
class AVLTree(BTS):
def __init__(self, li=None):
BTS.__init__(self, li) # 后面我们会重写插入操作的
def __rotate_left(self, p, c):
"""
左旋--->由右子树的右孩子多了造成的
:param p: 图中的p节点
:param c: 图中的c节点
s1 s2 s3 依次是节点带的小二叉树,看着图写代码最合适
:return: 返回旋转过后 当前子树 的根节点
我们会发现转移过后p,c平衡因子都变为0了。
(注意:若是删除左旋 那么和插入的左旋的bf值有可能不同,笔者删除还没写)
"""
# s2 与 p相连
s2 = c.lchild
p.rchild = s2
if s2: # 有s2我们才连父亲
s2.parent = p
# c 与 p相连
c.lchild = p
p.parent = c
# 更新bf
p.bf, c.bf = 0, 0
return c
def __rotate_right(self, p, c):
# 右旋--->由左子树的左孩子多了造成的,代码和上面类似
s2 = c.rchild
p.lchild = s2
if s2:
s2.parent = p
c.rchild = p
p.parent = c
c.bf, p.bf = 0, 0
return c
def __rotate_left_right(self, p, c):
# 左旋右旋--->由左子树的右孩子多了造成的
# 1.a 是 c右孩子 要变成该子树的根
a = c.rchild
s2 = a.lchild
s3 = a.rchild
# 2.我们要让s2,s3与c,p建立关系
c.rchild = s2
p.lchild = s3
if s2:
s2.parent = c
if s3:
s3.parent = p
# 3.我们让a与cp建立关系
a.lchild = c
c.parent = a
a.rchild = p
p.parent = a
# 4.更新bf 这时候我们得分三种情况
# 这三种情况分别是 1.s2上加了key,也就是说高度为h+2
# 2.s3上加了key,高度为h+2 3. s2.s3高度都为h+1,也就是说插入的是a
# 这三种情况不论怎么样,旋转完之后a.bf都为0
if a.bf < 0:
c.bf = 0
p.bf = 1
elif a.bf > 0:
c.bf = -1
p.bf = 0
else:
c.bf = 0
p.bf = 0
a.bf = 0
return a
def __rotate_right_left(self, p, c):
# 右旋左旋--->由右子树的左孩子多了造成的
# 和上面代码类似的思想
a = c.lchild
s2 = a.lchild
s3 = a.rchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = s3
if s3:
s3.parent = c
a.lchild = p
p.parent = a
a.rchild = c
c.parent = a
# 这里的bf建议画个图, 或者是看教材的书
if a.bf > 0: # key插入到s3上了
a.bf = 0
p.bf = -1
c.bf = 0
elif a.bf < 0: # 在s2上插入
a.bf = 0
p.bf = 0
c.bf = 1
else: # s1s2s3s4都是空 插入的是a
p.bf = 0
c.pf = 0
return a
def insert_no_rec(self, val):
p = self.root
if not self.root: # 根节点为空,特殊处理一下
self.root = AVLNode(val)
return
while True:
if p.data > val: # 要去左边插入
if p.lchild: # P有左孩子,往下面找
p = p.lchild
else: # p没有左孩子,插到它下面
p.lchild = AVLNode(val)
p.lchild.parent = p
node = p.lchild # 记录插入的点
break # 插入了我们就退出这个while循环 接着在对它进行旋转操作
elif p.data < val: # 要去右边插入
if p.rchild:
p = p.rchild
else:
p.rchild = AVLNode(val)
p.rchild.parent = p
node = p.rchild # 记录要插入的点
break
else: # 重复节点不做操作
return
# 更新bf,插入之后他的父亲的bf可能会变化,所以我们一定会一直往上面找,只要找到bf=0就可视为平衡二叉树
while node.parent: # node一开始是我们插入的点
if node.parent.lchild == node: # 加入的是左边,左边沉了,我们更新node.parent-1。
# 三种情况
if node.parent.bf < 0: # 本来是-1,我们一更新就减1变成-2了,我们就要旋转。这一句细细品味一下
x = node.parent # 记录一下未旋转情况下的根节点
g = node.parent.parent # 记录一下图p的父节点
if node.bf == -1: # 右旋(现在node就相当于图中的c)
n = self.__rotate_right(node.parent, node)
else: # node.bf只能为1
n = self.__rotate_left_right(node.parent, node)
elif node.parent.bf == 0: # 我们继续向上走
node.parent.bf = -1
node = node.parent
continue
else: # 发现加上之后bf=0了 我们就退出
node.parent.bf = 0
break
else: # 加的是右边 node.parent.bf += 1,思想和上面一样
if node.parent.bf == -1:
node.parent.bf = 0
break
elif node.parent.bf == 0:
node.parent.bf = 1
node = node.parent
continue
else:
x = node.parent
g = node.parent.parent
if node.bf == -1:
n = self.__rotate_right_left(node.parent, node)
else:
n = self.__rotate_left(node.parent, node)
# 链接更新完的树和原来的树
n.parent = g
if g: # g有的话下一步
if g.rchild == x: # 看未旋转之前是左孩子还是右孩子
g.rchild = n
else:
g.lchild = n
break
else:
self.root = n
break
avl = AVLTree([i for i in range(10, -1, -1)])
avl.in_order(avl.root) # 0,1,2,3,4,5,6,7,8,9,10,
print()
avl.pre_order(avl.root) # 7,3,1,0,2,5,4,6,9,8,10,
写者只是简单的实现了一下,例如一些内存删除可能不是特别完善,也可能有一些小的问题没有考虑清楚,若有问题可以随时和我沟通。