一 什么是AVL树(平衡二叉树):

AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。在AVL树中任何节点的两个子树的高度最大差别为一,所以它也被称为平衡二叉树。下面是平衡二叉树和非平衡二叉树对比的例图:

平衡因子(bf):结点的左子树的深度减去右子树的深度,那么显然-1<=bf<=1;

AVL树具有以下性质:

  • 根的左右子树的高度之差的绝对值不能超过1
  • 根的左右子树都是平衡二叉树

二 AVL树的旋转

插入一个节点可能会破坏AVL树的平衡, 可以通过旋转操作来进行修正
插入一个节点后,只有从插入节点到根节点的路径上的节点的平衡可能被改变。我们需要找出第一个破坏了平衡条件的节点,称之为K。K的两颗子树高度相差2
不平衡的出可能有4种情况:

  • 不平衡是由于对k的右孩子的右子树插入导致的:左旋
  • 不平衡是由于对k的左孩子的左子树插入导致的:右旋
  • 不平衡是由于对k的右孩子的左子树插入导致的:右旋-左旋
  • 不平衡是由于对k的左孩子的右子树插入导致的:左旋-右旋

1 左旋

我们在进行节点插入的时候,可能会出现节点都倾向于左边的情况,例如:

这个时候,我们就可以对节点9进行右旋操作,使它恢复平衡。

即:顺时针旋转两个节点,使得父节点被自己的左孩子取代,而自己成为自己的右孩子

再举个例子:

节点4和9高度相差大于1。由于是左孩子的高度较高,此时是左-左型,进行右旋。

2 左旋

左旋和右旋一样,就是用来解决当大部分节点都偏向右边的时候,通过左旋来还原。例如:

3 右旋左旋

对于图中画圈部分

单单一次左旋或右旋是不行的,下面我们先说说如何处理这种情况。

处理的方法是先对节点10进行右旋把它变成右-右型。

然后在进行左旋。

调整之后:

4 左旋右旋

同理,也存在左-右型的,例如:

对于左-右型的情况和刚才的 右-左型相反,我们需要对它进行一次左旋,再右旋。

在插入的过程中,会出现一下四种情况破坏AVL树的特性,我们可以采取如下相应的旋转。

1、左-左型:做右旋。

2、右-右型:做左旋转。

3、左-右型:先做左旋,后做右旋。

4、右-左型:先做右旋,再做左旋。

三 AVL树的实现代码

 class AVLNode(object):
def __init__(self, data):
'''
AVL树的每个节点
''' self.data = data
self.lchild = None
self.rchild = None
self.parent = None
self.bf = 0 class AVLTree(object):
'''
AVL树相关操作
''' def __init__(self, li=None):
self.root = None
if li:
for val in li:
self.insert_no_rec(val) 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) def rotate_left(self, p, c):
s2 = c.lchild
p.rchild = s2
if s2:
s2.parent = p
c.lchild = p
p.parent = c
p.bf = 0
c.bf = 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
p.bf = 0
c.bf = 0
return c def rotate_right_left(self, p, c):
g = c.lchild
s3 = g.rchild
c.lchild = s3
if s3:
s3.parent = c
g.rchild = c
c.parent = g s2 = g.lchild
p.rchild = s2
if s2:
s2.parent = p
g.lchild = p
p.parent = g # 更新bf
if g.bf > 0:
p.bf = -1
c.bf = 0
elif g.bf < 0:
p.bf = 0
c.bf = 1
else: # 插入的是g
p.bf = 0
c.bf = 0
return g def rotate_left_right(self, p, c):
g = c.rchild
s2 = g.lchild
c.rchild = s2
if s2:
s2.parent = c
g.lchild = c
c.parent = g s3 = g.rchild
p.lchild = s3
if s3:
s3.parent = p
g.rchild = p
p.parent = g # 更新bf
if g.bf < 0:
p.bf = 1
c.bf = 0
elif g.bf > 0:
p.bf = 0
c.bf = -1
else:
p.bf = 0
c.bf = 0
return g def insert_no_rec(self, val):
# 1 和BST一样插入
p = self.root
if not p: # 空树
self.root = AVLNode(val)
return
while True:
if val < p.data:
if p.lchild:
p = p.lchild
else: # 左子树不存在直接插入
p.lchild = AVLNode(val)
p.lchild.parent = p
node = p.lchild # node存储就是插入的节点
break
elif val > p.data:
if p.rchild:
p = p.rchild
else:
p.rchild = AVLNode(val)
p.rchild.parent = p
node = p.rchild
break
else: # val == p.data 一颗树如果插入同样的元素 不操作
return
# 更新balance factor
while node.parent: # node的parent不空
if node.parent.lchild == node: # 传递是从左子树来的, 左子树更沉了
# 更新node.parent的bf -=1
if node.parent.bf < 0: # 原来node.parent.bf == -1, 更新后变成-2
# 看node哪边沉
g = node.parent.parent # 为了连接旋转之后的子树
x = node.parent # 旋转之前子树的根
if node.bf > 0:
n = self.rotate_left_right(node.parent, node)
else:
n = self.rotate_right(node.parent, node)
elif node.parent.bf > 0: # 原来node.parent.bf=1 更新之后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf=0 更新之后变成-1
node.parent.bf = -1
node = node.parent
continue
else: # 传递是从右子树来的, 右子树更沉了
# 更新node.parent.bf += 1
if node.parent.bf > 0: # 原来node.parent.bf ==1, 更新后变成2
# 做旋转
# 看node那边沉
g = node.parent.parent # 为了连接旋转之后的子树
x = node.parent # 旋转之前子树的根
if node.bf < 0: # node.bf = 1
n = self.rotate_left_right(node.parent, node)
else: # node.bf = -1
n = self.rotate_left(node.parent, node) elif node.parent.bf < 0: # 原来node.parent.bf = -1 更新后变成0
node.parent.bf = 0
break
else: # 原来node.parent.bf =0 更新之后变成1
node.parent.bf = 1
node = node.parent
continue
# 链接旋转后的子树
n.parent = g
if g: # g不是空
if x == g.lchild:
g.lchild = n
else:
g.rchild = n
break
else:
self.root = n
break tree = AVLTree([9, 8, 7, 6, 5, 4, 3, 2, 1])
tree.pre_order(tree.root)
print("")
tree.in_order(tree.root)

数据结构树之AVL树(平衡二叉树)的更多相关文章

  1. 数据结构与算法——AVL树类的C++实现

    关于AVL树的简单介绍能够參考:数据结构与算法--AVL树简单介绍 关于二叉搜索树(也称为二叉查找树)能够參考:数据结构与算法--二叉查找树类的C++实现 AVL-tree是一个"加上了额外 ...

  2. Mysql为什么使用b+树,而不是b树、AVL树或红黑树?

    首先,我们应该考虑一个问题,数据库在磁盘中是怎样存储的?(答案写在下一篇文章中) b树.b+树.AVL树.红黑树的区别很大.虽然都可以提高搜索性能,但是作用方式不同. 通常文件和数据库都存储在磁盘,如 ...

  3. 图解数据结构树之AVL树

    AVL树(平衡二叉树): AVL树本质上是一颗二叉查找树,但是它又具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树.在AVL树中任何节点的两个子 ...

  4. 数据结构(三)实现AVL树

    AVL树的定义 一种自平衡二叉查找树,中面向内存的数据结构. 二叉搜索树T为AVL树的满足条件为: T是空树 T若不是空树,则TL.TR都是AVL树,且|HL-HR| <= 1 (节点的左子树高 ...

  5. 数据结构实验7:实现二分查找、二叉排序(查找)树和AVL树

    实验7 学号:      姓名:     专业: 7.1实验目的 (1) 掌握顺序表的查找方法,尤其是二分查找方法. (2) 掌握二叉排序树的建立及查找. 查找是软件设计中的最常用的运算,查找所涉及到 ...

  6. 数据结构与算法分析-AVL树

    1.AVL树是带有平衡条件的二叉查找树. 2.AVL树的每个节点高度最多相差1. 3.AVL树实现的难点在于插入或删除操作.由于插入和删除都有可能破坏AVL树高度最多相差1的特性,所以当特性被破坏时需 ...

  7. 数据结构——二叉查找树、AVL树

    二叉查找树:由于二叉查找树建树的过程即为插入的过程,所以其中序遍历一定为升序排列! 插入:直接插入,插入后一定为根节点 查找:直接查找 删除:叶子节点直接删除,有一个孩子的节点删除后将孩子节点接入到父 ...

  8. "《算法导论》之‘树’":AVL树

    本文关于AVL树的介绍引自博文AVL树(二)之 C++的实现,与二叉查找树相同的部分则不作介绍直接引用:代码实现是在本文的基础上自己实现且继承自上一篇博文二叉查找树. 1.AVL树的介绍 AVL树是高 ...

  9. [数据结构与算法] : AVL树

    头文件 typedef int ElementType; #ifndef _AVLTREE_H_ #define _AVLTREE_H_ struct AvlNode; typedef struct ...

随机推荐

  1. 进阶路上有你我-相互相持篇之ES6里箭头函数里的this指向问题

    首先复习下普通函数里的this指向: function test(){ console.log(this) } test() 你会秒杀的毫无疑问的回答:window,针对普通函数:谁调用了函数  函数 ...

  2. java.lang.Long 类源码解读

    总体阅读了Long的源码,基本跟Integer类类似,所以特别全部贴出源码,直接注释进行理解. // final修饰符 public final class Long extends Number i ...

  3. WordPress版微信小程序3.1.5版的新功能

    产品的完善是无止境,每过段时间就会发现产品的新问题,使用的人越多,提的需求也会越多,我听得最多的一句话就是:如果加上某某功能就完美了.其实,完美是不存在的,每个人的视角不一样,完美的定义也是不一样的. ...

  4. Ubuntu下安装Snap

    介绍 Snap是一个全新的软件包架构,它与其它包管理器的区别在于snap安装的app互相之间是高度隔离的,减少了互相引用. 避免了很多冲突问题. 不过这也导致了其占用的磁盘比较多. 安装 apt in ...

  5. [转]AJAX POST请求中参数以form data和request payload形式在servlet中的获取方式

    转载至 http://blog.csdn.net/mhmyqn/article/details/25561535 最近在写接收第三方的json数据, 因为对java不熟悉,有时候能通过request能 ...

  6. (转)K-近邻算法(KNN)

    K-近邻算法(KNN)概述  KNN是通过测量不同特征值之间的距离进行分类.它的思路是:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别 ...

  7. Linux内核中的printf实现

    1 #ifndef __PRINT_H_ 2 #define __PRINT_H_ 3 4 void print(char* fmt, ...); 5 void printch(char ch); 6 ...

  8. 小程序坑之 swiper组件

    表现:swiper 内容 空白 原因:swiper组件的current值为n时,重新刷新页面,current值不变,当刷新后的swiper item的数量少于 n 时,swpier找不到对应的item ...

  9. 利用JAVA API函数实现数据的压缩与解压缩

      综述 许多信息资料都或多或少的包含一些多余的数据.通常会导致在客户端与服务器之间,应用程序与计算机之间极大的数据传输量.最常见的解决数据存储和信息传送的方法是安装额外的存储设备和扩展现有的通讯能力 ...

  10. react-native上手篇

    根据公司发展,后期可能要做APP开发,所以了解一下react-native.之前工作用过react,所以想想应该不会太难.(结果配置环境和demo就搞了一天!) 1,搭建环境 官网地址 1,Node( ...