AVL树是带有平衡条件的二叉查找树。
为什么需要平衡捏?
之前看的二叉查找树,在进行大量插入与删除操作时,会出现左子树越来越深(可以理解为元素节点越来越多,比右子树多很多)的情况,因为删除时经常把右子树中的节点拿出来替代删除的节点,这样在对树操作时会降低效率。因为操作时大多会往左检索,而需要检索的节点会越来越多。
所以,如果使树维持在左右两部分大致差不多的情况,在差不多的数据量下,效率明显更高。
一棵AVL树,其每个节点的左右子树的高度最多相差1。
如果一个节点的左右子树的高度相差超过1,我现在称它为不平衡。
为了保证每个节点的左右子树的高度最多相差1。
首先对删除操作实行懒惰删除。即当要删除一个节点上,只是对其进行标记,并不真的将其删除,其任然留在树中。这特别是在有重复项时很常用,因为此时记录出现频率数的域可以减1。如果树中的实际节点数和“被删除”的节点数相同,那么树的深度预计只上升一个小的常数。如果被删除的项是重新插入的,那么分配一个新单元的开销就避免了。
对插入操作,我们需要更新通向根节点路径上那些节点的所有平衡信息,判断是否让树平衡被打破,即树中某一节点的左右子树的高度最多相差超过1。如果平衡被破坏,则需要修正,称其为旋转。
简单来说,会破坏平衡的操作(插入)有四种:
1左左–左;2左左–右;3右右–左;4右右–右。1和4镜像对称,2和3镜像对称。
1和4需要一次单旋转,2和3则需要复杂些的双旋转。
单旋转
对于什么时候单旋转,即1和4的情况,我觉得可以这样理解:
即插入的打破平衡的新元素(A),并不在被打破平衡的节点(B)(该节点在插入新元素后左右子树的高度最相差2>1),与B到A的路路径的儿子节点(C)之间。
1左(B)左(C)–左(A):A比B和C都小。
4右(B)右(C)–右(A):A比B和C都大。
(2和3的情况就可以简单的理解为A在B和C之间,双旋转)
(备注:C是B的儿子节点,A是C的子节点,但A不一定是C的儿子节点,A可能距离B很远,但是它的插入却会使B不平衡)
此时进行单旋转。单旋转怎么旋转捏?
谁被打破,旋转谁,即改变谁。可以很明显看出应该旋转B,即用B的儿子C替换B,
对1左(B)左(C)–左(A)的情况就是 :C原本是B的左儿子,让其替换到B的位置,C成为父节点,B成为其右儿子。
对4右(B)右(C)–右(A))的情况就是:C原本是B的右儿子,让其替换到B的位置,C成为父节点,B成为其左儿子。
此时树就又平衡了。(//没有图,真是不知道怎么解释,如果有图,应该一目了然)
双旋转
双旋转实际就是两次单旋转
对于2和3的情况,单旋转不能使树平衡。(可以画图试试)。所以我们先旋转一次,使其成为1和4的情况。
此时被改变的任然是B,因为它不平衡了嘛。而此时不是用B的儿子C来代替B,而是用C的儿子节点来替换B。
若A就是C的儿子节点,则直接用A代替B,若A不是C的儿子节点,则再引入C的一个儿子节点D来进行讨论。
若A是C的左子节点(即A在C的左子树上),则D为C的左儿子节点。同理,若A是C的右子节点(即A在C的右子树上),则D为C的右儿子节点。
此时用D来代替B。
再让树满足二叉查找树的基本条件后,树就右平衡了。
本篇不敢标记为转载,因为不是照打书上的类容,上面均是个人理解。勿盲信!
伸展树
按照前面所说,为了避免二叉树太容易变得很深,提出了AVL树。但是现实操作中,二叉树并不是那么容易变的极端深(你不能每次插入都往左节点去吧)。也就说我们可能并不需要频繁的去进行旋转操作。 伸展树的基本想法是,当一个节点被访问,它就要经过一系列的AVL树的旋转被推到根上。