一、摘要
在上篇文章,我们详细的介绍了二叉树的算法以及代码实践,我们知道不同的二叉树形态结构,对查询效率也会有很大的影响,尤其是当树的形态结构变成一个链条结构的时候,查询最后一个元素的效率极底,如何解决这个问题呢?
关键在于如何最大限度的减小树的深度,从而提高查询效率,正是基于这一点,平衡二叉查找树出现了!
平衡二叉查找树,算法由Adel'son-Vel'skii
和 Landis
两位大神发明,同时也俗称AVL 树
,来自两位大神的姓名缩写,特性如下:
- 它的左子树和右子树都是平衡二叉树;
- 且它的左子树和右子树的深度之差的绝对值(平衡因子 ) 不超过1;
简单的说,就是为了保证平衡,当前节点的左子树、右子树的高度差不超过1!
废话也不多说了,直奔主题,算法思路如下!
二、算法思路
平衡二叉查找树的查找思路,与二叉树是一样,每次查询的时候对半分,只查询一部分,以达到提供效率的目的,插入、删除也一样,最大的不同点:每次插入或者删除之后,需要计算节点高度,然后按需进行调整!
如何调整呢?主要方法有:左旋转、右旋转!
下面我们分别来分析一下插入、删除的场景调整。
2.1、插入场景
我们来分析一下插入的场景,如下:
场景一
当我们在40
的左边或者右边插入的时候,也就是50
的左边,只需绕80
进行右旋转,即可达到树高度差不超过1!
场景二
当我们在60
的左边或者右边插入的时候,也就是50
的右边,需要进行两次旋转,先会绕50
左旋转一次,再绕80
右旋转一次,即可达到树高度差不超过1!
场景三
当我们在120
的左边或者右边插入的时候,也就是90
的右边,只需绕80
进行左旋转,即可达到树高度差不超过1!
场景四
当我们在85
的左边或者右边插入的时候,也就是90
的左边,需要进行两次旋转,先会绕90
右旋转一次,再绕80
左旋转一次,即可达到树高度差不超过1!
总结
对于插入这种操作,总共其实只有这四种类型的插入,即:单次左旋转、单次右旋转、左旋转-右旋转、右旋转-左旋转,总结如下:
- 当插入节点位于需要旋转节点的左节点的左子树时,只需右旋转;
- 当插入节点位于需要旋转节点的左节点的右子树时,需要左旋转-右旋转;
- 当插入节点位于需要旋转节点的右节点的右子树时,只需左旋转;
- 当插入节点位于需要旋转节点的右节点的左子树时,需要右旋转-左旋转;
2.2、删除场景
接下来,我们分析一下删除场景!
其实,删除场景跟二叉树的删除思路是一样的,不同的是需要调整,删除的节点所在树,需要层层判断节点的高度差是否大于1,如果大于1,就进行左旋转或者右旋转!
场景一
当删除的节点,只有左子树时,直接将左子树转移到上层即可!
场景二
当删除的节点,只有右子树时,直接将右子树转移到上层即可!
场景三
当删除的节点,有左、右子树时,因为当前节点的左子树的最末端的右子树或者当前节点的右子树的最末端的左子树,最接近当前节点,找到其中任意一个,然后将其内容替换并移除最末端节点,即可实现删除!
总结
第三种场景稍微复杂了一些,但基本都是这么一个套路,与二叉树不同的是,删除之后需要判断树高,对超过1的进行调整,类似上面插入的左旋转、右旋转操作!
三、代码实践
接下来,我们从代码层面来定义一下树的实体结构,如下:
public class AVLNode<E extends Comparable<E>> {
/**节点关键字*/
E key;
/**当前节点树高*/
int height;
/**当前节点的左子节点*/
AVLNode<E> lChild = null;
/**当前节点的右子节点*/
AVLNode<E> rChild = null;
public AVLNode(E key) {
this.key = key;
}
@Override
public String toString() {
return "AVLNode{" +
"key=" + key +
", height=" + height +
", lChild=" + lChild +
", rChild=" + rChild +
'}';
}
}
我们创建一个算法类AVLSolution
,完整实现如下:
public class AVLSolution<E extends Comparable<E>> {
/**定义根节点*/
public AVLNode<E> root = null;
/**
* 插入
* @param key
*/
public void insert(E key){
System.out.println("插入[" + key + "]:");
root = insertAVL(key,root);
}
private AVLNode insertAVL(E key, AVLNode<E> node){
if(node == null){
return new AVLNode<E>(key);
}
//左子树搜索
if(key.compareTo(node.key) < 0){
//当前节点左子树不为空,继续递归向下搜索
node.lChild = insertAVL(key,node.lChild);
//出现不平衡,只会是左子树比右子树高,大于1的时候,就进行调整
if(getHeight(node.lChild) - getHeight(node.rChild) == 2){
if(key.compareTo(node.lChild.key