二叉排序树集中了数组的查找优势以及链表的插入、删除优势,因此在数据结构中占有一定的地位。但在一定的情况下二叉排序树又有可能变为链表,例如插入从1~100的数,这时进行数据查找的效率就要降低。
为了解决二叉排序树这种左右子树深度不均匀的情况引入了一种平衡二叉树(AVLTree):任何一个节点的左右子树深度差不超过1.通过这个限定,阻止了二叉树的左右子树深度差较大的情况,维持了二叉树的稳定。
如何让二叉树的左右子树深度差不超过1呢?这就需要对节点进行旋转,也就是当某个节点的左右子树深度超过1时需要对这个节点进行旋转(旋转之后依旧是左子树小于节点小于右子树),重新调整树的结构。
例如:这两棵二叉树虽然结构不同,但是都是二叉排序树,所谓的旋转就是把左边的深度为3的树旋转为右边深度为2的二叉树。
在平衡二叉树进行插入操作时遇到的不平衡情况有多种,但是这么多种情况都可以分解为一下四中基础情景:把它叫做:左左、左右、右右、右左。
在解释这四种情景之前需要先明白一个定义:最小不平衡节点—插入一个节点之后,距离这个插入节点最近的不平衡节点就是最小不平衡节点(如上图左树的10节点)。所有的旋转都是在最小不平衡节点的基础上进行的。
继续解释四种情景命名意义:左左:节点插入在最小不平衡节点的左子树的左子树上。 左右:节点插入在最小不平衡节点的左子树的右子树上面
右:节点插入在最小不平衡树的右子树的右子树上面。 右左:节点插入在最小不平衡树的右子树的左子树上面。
下面就具体分析这四种情况:
左左:右旋
左左简单不用详解。
左右:先左旋再右旋
这里有人又有疑问了,上面的左左(图2)看明白了,可这里左右情景为什么要旋转两次呢?为什么先左旋,再右旋呢?
先别急,看看这种情况:(图4)
毫无疑问这也是左右情景(左左情景有很多种,图3演示的是最基础的情景,所有的左左情景的旋转情况和图3都是一样的),那么该怎么旋转呢?
直接右旋不对吧?因为6节点的右子树(以根节点10为中心,靠近内部的子树)6-8经过旋转之后要充当10节点的左子树,这样会导致依旧不平衡。所以在这种左右情景下需要进行两次旋转,先把6的右子树降低高度,然后在进行右旋。即:
把图7 情景和图3的情景一样,这就是为什么左右情景需要先左旋再右旋的原因。
在这里可以记作:最小不平衡节点的左节点的内部(以根节点做对称轴,偏向对称轴的为内部。也就是以7为节点的子树)的子树高度高于外部子树的高度时需要进行两次旋转。
右右:左旋
右右情景直接左旋即可。不在详解
右左:先右旋,再左旋
为什么这样旋转明白了吧?如同左右情景,考虑到图10的右左情景
这种情景旋转如图11
旋转的四种情景就这些了。需要说明的是,下面这两对情景旋转是一样的。
图12都是右左情景,具体看代码的旋转方法就明白了在第一次右旋的时候进行的操作。privateNode<T> rotateSingleRight(Node<T> node);
图13都是左右情景,第一次左旋见:privateNode<T> rotateSingleLeft(Node<T> node);
旋转情景弄明白之后就是怎么代码实现了,在实现代码之前需要考虑如何进行树高判断。这里就根据定义来,|左子树树高-右子树树高|<2。如果大于等于2则该节点就不在平衡,需要进行旋转操作。因此在程序中节点中需要定义一个height属性来存储该节点的树高。
由于平衡二叉树的性质,二叉树的高度不会很高,程序使用递归进行数据插入查找不会造成栈溢出异常,所以程序采用递归操作进行插入查找。
平衡的判定策略是在进行递归回溯的时候依照回溯路径更新节点的树高,然后根据|左子树树高-右子树树高|<2来判定该节点是否失衡,进一步对是够旋转进行判定。
平衡二叉树的删除操作。
对于平衡二叉树的删除操作,只要明白一点就可以了:
如果该节点没有左右子树(该节点为叶子节点)或者只有其中一个子树则可以直接进行删除
否则需要继续进行判定该节点:如果该节点的外部(内外:以根节点做对称轴,靠近对称轴的子树为内部子树)子树树高低于内部子树树高,则找到该节点内部子树的最值(最值:如果内部子树是该节点的右子树则数值为右子树的最小值;如果内部节点是该节点的左子树则数值为该节点左子树的最大值)进行数值交换,交换之后删除该节点即可。
删除之后进行回溯的时候要更新节点的树高,然后判断节点是否平衡,不平衡进行旋转。这时对旋转次数的判定就不同于插入时的判定。
如图14 删除11节点
这种情景需不需要进行两次旋转?该如何判定?
毫无疑问肯定是要进行一次右旋的,但是在右旋之前是不是要进行一次左旋呢?
这就要根据最小不平衡节点的左节点6进行判定,如果6的左节点树高低于6的右节点树高则需要进行一次左旋,最后进行一次右旋结束。
如果6的左子树树高高于6的右子树树高则不需进行左旋可以直接对10节点进行右旋结束操作。
如图15,这种情况肯定需要进行左旋,至于在左旋之前要不要对13节点进行右旋,相信知道该如何判断了。
根据13节点的左右子树高度来判断,左子树(内部)高于右子树(外部)高度则需要进行左旋,图15这种情景是不需要的。