二叉平衡树原理及python实现
二叉平衡树(AVL树)基本定义
- AVL树里任一结点的左右子树高度(深度)差的绝对值小于等于1,绝对值就是平衡因子
- 任一结点的左右子树均为AVL树
AVL树是一种特殊的二叉搜索树 (BST树),数据极端情况下, 二叉搜索树会退化成为单链表,但是AVL树通过旋转操作规避了这个问题。
查找平均复杂度:O(logn)
下面是基本节点定义
class TreeNode(object):
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class AVLTree(object):
def __init__(self):
self.root = None
利用递归计算二叉平衡树的深度(高度)
注:下面的代码都是属于类AVLTree里的方法
def depth(self, node):
if node is None:
return 0
d1 = self.depth(node.left) # 计算node节点左子树的深度
d2 = self.depth(node.right) # 计算node节点右子树的深度
return max(d1, d2) + 1
二叉平衡树之插入节点
首先,二叉平衡树一棵二叉搜索树,其添加节点的方式同二叉搜索树一致。但按照这种方式添加(插入)节点会导致树中的某节点的平衡因子大于1,所以为了使得此树满足二叉平衡树的定义,在插入节点后需要对树中的不平衡点进行调整。根据不同的插入位置,一共有以下四种调整方法。
插入位置 | 调整方式 |
---|---|
插入结点在不平衡点右子树的右边 | LL调整,左旋转 |
插入结点在不平衡点左子树的左边 | RR调整,右旋转 |
插入结点在不平衡点左子树的右边 | LR调整,左右旋转 |
插入结点在不平衡点右子树的左边 | RL调整,右左旋转 |
注:不平衡点是第一个不满足这棵树是平衡二叉树的结点(平衡因子大于1),如果有多个结点同时不满足,就选最靠近下面的那个结点作为不平衡点。
RR调整
插入节点是2,上图的10是不平衡点,其左子树高度为2,右子树高度为0,平衡因子为2。需要进行右旋转,RR调整的代码如下:
def right_rotate(self, root): # root是不平衡点
if root is None:
return
# 创建新的结点,以当前根结点的值
new_root = TreeNode(root.val)
# 把新结点的右子树设为当前结点的右子树
new_root.right = root.right
# 把新结点的左子树设为当前结点的左子树的右子树
new_root.left = root.left.right
# 把当前结点的值替换成它的左子结点的值
root.val = root.left.val
# 把当前结点的左子树设置成当前结点的左子树的左子树
root.left = root.left.left
# 把当前结点的右子结点设置成(指向)新的结点
root.right = new_root
RR调整后,整棵树如下图所示,满足二叉平衡树的定义
LL调整
插入节点是9,上图的7是不平衡点,其左子树高度为0,右子树高度为2,平衡因子为2。需要进行左旋转,LL调整的代码如下:
def left_rotate(self, root): # root是不平衡点
if root is None:
return
# 创建新的结点,以当前根结点的值
new_root = TreeNode(root.val)
# 把新结点的左子树设为当前结点的左子树
new_root.left =root.left
# 把新结点的右子树设为当前结点的右子树的左子树
new_root.right = root.right.left
# 把当前结点的值替换成它的右子结点的值
root.val = root.right.val
# 把当前结点的右子树设置成当前结点的右子树的右子树
root.right = root.right.right
# 把当前结点的左子结点设置成(指向)新的结点
root.left = new_root
LL调整后,整棵树如下图所示,满足二叉平衡树的定义
LR调整
插入节点是6,上图的7是不平衡点,其左子树高度为2,右子树高度为0,平衡因子为2。需要进行左右旋转(先对5节点进行左旋转变成RR调整的形式,再对7节点进行右旋转),LR调整的代码如下:
def left_right_rotate(self, root):
self.left_rotate(root.left)
self.right_rotate(root)
先进行左旋转,整棵树如下图所示,化成RR调整的形式
再进行右旋转,整棵树如下图所示,满足平衡二叉树的定义。
RL调整
插入节点是8,上图的7是不平衡点,其左子树高度为0,右子树高度为2,平衡因子为2。需要进行右左旋转(先对9节点进行右旋转变成LL调整的形式,再对7节点进行左旋转),RL调整的代码如下:
def right_left_rotate(self, root):
self.right_rotate(root.right)
self.left_rotate(root)
先进行右旋转,整棵树如下图所示,化成LL调整的形式
再进行左旋转,整棵树如下图所示,满足平衡二叉树的定义。
二叉平衡树之删除节点
二叉平衡树之寻找前继节点和后继节点
本文在不含父亲节点的数据结构下,分析给出了时间复杂度为O(lgN)的求前驱后继结点的算法。
前驱节点
若一个节点有左子树,那么该节点的前驱节点是其左子树中val值最大的节点.
若一个节点没有左子树,那么判断该节点和其父节点的关系.
- 若该节点是其父节点的右边孩子,那么该节点的前驱结点即为其父节点。
- 若该节点是其父节点的左边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到一个节点P,P节点是其父节点Q的右边孩子(可参考例子2的前驱结点是1),那么Q就是该节点的后继节点
类似,我么可以得到求后继节点的规则。
后继节点
若一个节点有右子树,那么该节点的后继节点是其右子树中val值最小的节点(也就是右子树中所谓的leftMostNode)
若一个节点没有右子树,那么判断该节点和其父节点的关系
2.1 若该节点是其父节点的左边孩子,那么该节点的后继结点即为其父节点
2.2 若该节点是其父节点的右边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到一个节点P,P节点是其父节点Q的左边孩子,那么Q就是该节点的后继节点
规则中我们是从下往上找,但实际代码中是不允许我们这么操作的(由于我们没有父亲指针),我们可以在寻找对应val节点的过程中从上向下找,并且过程中记录下parent节点和firstRParent节点(最后一次在查找路径中出现右拐的节点)。
python代码实现如下:
def getPreNode_val(self, val): # 默认该二叉平衡树非空,有根节点
flag = True # 默认 值为val的节点没有左子树
root = self.root
Parent = None
firstRParent = None
while root:
if val == root.val:
node = root # 值为val的节点
if root.left:
flag = False
break
Parent = root
if val < root.val:
root = root.left
elif val > root.val:
firstRParent = root # 出现右拐点
root = root.right
if root is None: # 表明上述循环没有找到节点val
print("值为%d的节点不存在前继节点"%val)
return
if Parent is None: # 表示上述循环内的Parent=root语句一次也未执行,此时查询的点即根节点
node = root.left
if node is None:
return root.val
else:
while node.right: # 只要一直有右孩子,就一直循环
node = node.right
return node.val
else: # 查询的节点不是根节点
if flag: # node节点没有左子树
if val > Parent.val: # node节点是父亲节点的右孩子,此时Parent等于firstParent,firstParent没有意义
return Parent.val
elif val < Parent.val: # node节点是父亲节点的左孩子,此时Parent不等于firstParent,firstParent有意义
if firstRParent is None:
return None # 没有前继节点
else:
return firstRParent.val
else: # node节点有左子树
node = node.left
while node.right: # 只要一直有右孩子,就一直循环
node = node.right
return node.val # 对于值为val的节点node,返回其左子树中val值最大的节点
求后继节点
同样,求后继节点我们不能从底向上找,也是从上向下找,首先是找到对应val值的节点,顺便把其的parent节点和firstlParent节点(最后一次在查找路径中出现左拐的节点)。
python代码实现如下:
def getNextNode_val(self, val):
flag = True # 默认 值为val的节点没有右子树
root = self.root
Parent = None
firstLParent = None
while root:
if val == root.val:
node = root # 值为val的节点
if root.right: # 有右孩子
flag = False
break
Parent = root
if val < root.val:
firstLParent = root # 出现左拐点
root = root.left
elif val > root.val:
root = root.right
if root is None:
print("值为%d的节点不存在后继节点"%val)
return
if Parent is None: # 表示上述循环内的Parent=root语句一次也未执行,此时查询的点即根节点
node = root.right
if node is None:
return root.val
else:
while node.left: # 只要一直有左孩子,就一直循环
node = node.left
return node.val
else: # 查询的节点不是根节点
if flag: # node节点没有右子树
if val < Parent.val: # node节点是父亲节点的左孩子,此时Parent等于firstParent,firstParent没有意义
return Parent.val
elif val > Parent.val: # node节点是父亲节点的右孩子,此时Parent不等于firstParent,firstParent有意义
if firstLParent is None:
return None
else:
return firstLParent.val
else: # node节点有右子树
node = node.right
while node.left: # 只要一直有左孩子,就一直循环
node = node.left
return node.val # 对于值为val的节点node,返回其左子树中val值最大的节点
判断是否该二叉树是否平衡
def isblanced(self, root):
def height(node):
if not node:return 0
left = height(node.left)
right = height(node.right)
if left == -1 or right == -1 or abs(left-right) > 1:
return -1
return max(left,right) + 1
return height(root) != -1
二叉平衡树之先序遍历、中序遍历
# 中序遍历测试
def in_order(self, node):
if node is None:
return
self.in_order(node.left)
print(node.val, end=" ")
self.in_order(node.right)
# 前序遍历测试(主要看根结点的变化)
def pre_order(self, node):
if node is None:
return
print(node.val, end=" ")
self.pre_order(node.left)
self.pre_order(node.right)
二叉平衡树全部代码
class TreeNode(object):
def __init__(self, val):
self.val = val
self.left = None
self.right = None
class AVLTree(object):
def __init__(self):
self.root = None
self.P = 0
def getpower(self):
return self.P
def depth(self, node):
if node is None:
return 0
d1 = self.depth(node.left)
d2 = self.depth(node.right)
return max(d1, d2) + 1
def left_rotate(self, root):
if root is None:
return
# 创建新的结点,以当前根结点的值
new_root = TreeNode(root.val)
# 把新结点的左子树设为当前结点的左子树
new_root.left =root.left
# 把新结点的右子树设为当前结点的右子树的左子树
new_root.right = root.right.left
# 把当前结点的值替换成它的右子结点的值
root.val = root.right.val
# 把当前结点的右子树设置成当前结点的右子树的右子树
root.right = root.right.right
# 把当前结点的左子结点设置成(指向)新的结点
root.left = new_root
def right_rotate(self, root):
if root is None:
return
# 创建新的结点,以当前根结点的值
new_root = TreeNode(root.val)
# 把新结点的右子树设为当前结点的右子树
new_root.right = root.right
# 把新结点的左子树设为当前结点的左子树的右子树
new_root.left = root.left.right
# 把当前结点的值替换成它的左子结点的值
root.val = root.left.val
# 把当前结点的左子树设置成当前结点的左子树的左子树
root.left = root.left.left
# 把当前结点的右子结点设置成(指向)新的结点
root.right = new_root
def left_right_rotate(self, root):
self.left_rotate(root.left)
self.right_rotate(root)
def right_left_rotate(self, root):
self.right_rotate(root.right)
self.left_rotate(root)
"""# 非递归添加结点
def insert(self, node):
if self.root is None:
self.root = node
return
queue = [self.root]
while queue:
temp_node = queue.pop(0)
# 判断传入结点的值和当前子树结点的值关系
if node.val < temp_node.val:
if temp_node.left is None:
temp_node.left = node
return
else:
queue.append(temp_node.left)
if node.val >= temp_node.val:
if temp_node.right is None:
temp_node.right = node
return
else:
queue.append(temp_node.right)
"""
# 递归添加结点
def insert(self, root, val):
node = TreeNode(val)
if root is None:
self.root = node
return
if val < root.val:
if not root.left: # 根的左子树是空的
root.left = node
return
else:
self.insert(root.left, val)
if self.depth(root.left) - self.depth(root.right) >= 2:
if val < root.left.val:
self.right_rotate(root)
else:
self.left_right_rotate(root)
else:
if not root.right: # 根的右子树是空的
root.right = node
return
else:
self.insert(root.right, val)
if self.depth(root.right) - self.depth(root.left) >= 2: # 这里的root就是指“递归添加结点那里传入的root”
if val > root.right.val:
self.left_rotate(root)
else:
self.right_left_rotate(root)
def remove(self, root, val):
def isblanced(self, root):
def height(node):
if not node:return 0
left = height(node.left)
right = height(node.right)
if left == -1 or right == -1 or abs(left-right) > 1:
return -1
return max(left,right) + 1
return height(root) != -1
def getPreNode_val(self, val): # 默认该二叉平衡树非空,有根节点
flag = True # 默认 值为val的节点没有左子树
root = self.root
Parent = None
firstRParent = None
while root:
if val == root.val:
node = root # 值为val的节点
if root.left:
flag = False
break
Parent = root
if val < root.val:
root = root.left
elif val > root.val:
firstRParent = root # 出现右拐点
root = root.right
if root is None: # 表明上述循环没有找到节点val
print("值为%d的节点不存在前继节点"%val)
return
if Parent is None: # 表示上述循环内的Parent=root语句一次也未执行,此时查询的点即根节点
node = root.left
if node is None:
return root.val
else:
while node.right: # 只要一直有右孩子,就一直循环
node = node.right
return node.val
else: # 查询的节点不是根节点
if flag: # node节点没有左子树
if val > Parent.val: # node节点是父亲节点的右孩子,此时Parent等于firstParent,firstParent没有意义
return Parent.val
elif val < Parent.val: # node节点是父亲节点的左孩子,此时Parent不等于firstParent,firstParent有意义
if firstRParent is None:
return None # 没有前继节点
else:
return firstRParent.val
else: # node节点有左子树
node = node.left
while node.right: # 只要一直有右孩子,就一直循环
node = node.right
return node.val # 对于值为val的节点node,返回其左子树中val值最大的节点
def getNextNode_val(self, val):
flag = True # 默认 值为val的节点没有右子树
root = self.root
Parent = None
firstLParent = None
while root:
if val == root.val:
node = root # 值为val的节点
if root.right: # 有右孩子
flag = False
break
Parent = root
if val < root.val:
firstLParent = root # 出现左拐点
root = root.left
elif val > root.val:
root = root.right
if root is None:
print("值为%d的节点不存在后继节点"%val)
return
if Parent is None: # 表示上述循环内的Parent=root语句一次也未执行,此时查询的点即根节点
node = root.right
if node is None:
return root.val
else:
while node.left: # 只要一直有左孩子,就一直循环
node = node.left
return node.val
else: # 查询的节点不是根节点
if flag: # node节点没有右子树
if val < Parent.val: # node节点是父亲节点的左孩子,此时Parent等于firstParent,firstParent没有意义
return Parent.val
elif val > Parent.val: # node节点是父亲节点的右孩子,此时Parent不等于firstParent,firstParent有意义
if firstLParent is None:
return None
else:
return firstLParent.val
else: # node节点有右子树
node = node.right
while node.left: # 只要一直有左孩子,就一直循环
node = node.left
return node.val # 对于值为val的节点node,返回其左子树中val值最大的节点
# 中序遍历测试
def in_order(self, node):
if node is None:
return
self.in_order(node.left)
print(node.val, end=" ")
self.in_order(node.right)
# 前序遍历测试(主要看根结点的变化)
def pre_order(self, node):
if node is None:
return
print(node.val, end=" ")
self.pre_order(node.left)
self.pre_order(node.right)
if __name__ == '__main__':
a = AVLTree()
# 测试左旋转结点值数组
# node_array = [4, 3, 6, 5, 7, 8]
# 测试右旋转结点值数组
# node_array = [10, 12, 8, 9, 7, 6]
# 测试双旋转结点数组
node_array = [10, 11, 7, 6, 8, 9]
for item in node_array:
a.insert(a.root, item)
# a.insert(item)
# a.judge_node(a.root)
print("中序遍历结果为:")
a.in_order(a.root)
print()
print("旋转后,前序遍历结果为:")
a.pre_order(a.root)
print()
print("树的高度为:", a.depth(a.root))
print("左子树高度为:", a.depth(a.root.left))
print("右子树高度为:", a.depth(a.root.right))
如果本文章对你有帮助的话,麻烦点个赞哦,谢谢!!!