一.AVL树是什么
在前面的学习中我们已经学习了二叉搜素树,二叉搜素树主要用于查询。二叉搜素树的查询效率为o(n),当树有序的时候二叉搜素树就变为一颗单分支的树,树的高度为n,所以最坏情况下时间复杂度为o(n)。
二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过1(需要对树中的结点进行调整),即可降低树的高度,从而减少平均搜索长度。
AVL树的特点:1.他的左右子树都是AVL树
2.左右子树高度之差不超过1(右子树减去左子树高度小于1)
二.AVL树节点的定义
class AVLTree {
static class TreeNode {
public int val;
public int bf;//平衡因子
public TreeNode left;//左孩子的引用
public TreeNode right;//右子树的引用
public TreeNode parent;//父亲节点的引用
public TreeNode(int val) {
this.val = val;
}
}
我们需要把AVL定义为三叉树,同时增加bf记录每个节点的平衡因子,通过获取节点的平衡因子来调节树的平衡。三叉结构增加一个指针指向父亲节点,可以更好的在增加节点时修改父亲节点的平衡因子。
三.AVL树的插入
AVL树就是在二叉搜索树的基础上引入了平衡因子,因此AVL树也可以看成是二叉搜索树。那么AVL树的插入过程可以分为两步:1. 按照二叉搜索树的方式插入新节点。2. 调整节点的平衡因子
public boolean insert(int val) {
TreeNode node = new TreeNode(val);
if(root == null) {
root = node;
return true;
}
TreeNode parent = null;
TreeNode cur = root;
while (cur != null) {
if(cur.val < val) {
parent = cur;
cur = cur.right;
}else if(cur.val == val) {
return false;
}else {
parent = cur;
cur = cur.left;
}
}
//cur == null
if(parent.val < val) {
parent.right = node;
}else {
parent.left = node;
}
//
node.parent = parent;
cur = node;
上述代码为二叉搜素树的插入,前面已经学习过,但注意相比于二叉搜素树AVL树多了每个节点的parent域必须指向父亲节点。
如果在插入节点时,插入的节点比父亲节点大,那么该节点在父亲节点的右分支,bf++,反之如比父亲节点小,应当在父节点的左分支,所以bf–。
while (parent != null) {
//先看cur是parent的左还是右 决定平衡因子是++还是--
if(cur == parent.right) {
//如果是右树,那么右树高度增加 平衡因子++
parent.bf++;
}else {
//如果是左树,那么左树高度增加 平衡因子--
parent.bf--;
}
我们每插入一个节点都需要更新父亲节点的平衡因子,总共可分为三种情况:
1.如果更新后父亲节点的平衡因子为0,说明没有插之前父亲节点的平衡因子为1或者-1。在插入该节点后变为0,又说明正好插入矮的一边父亲节点因此平衡,此时树的高度不变,不用继续向上调整。
2.如果更新后平衡因子为1或者-1,说明未插入之前父亲节点的高度为0,插入后父亲节点变得不平衡,此时需要向上调整更新祖先节点的高度,最多会更新到根节点。
3.如果向上调整的过程中某个节点的平衡因子变为2或者-2此时该树不平衡,不是AVL树,我们需要调整这颗树使其成为AVL树,要将其进行旋转。
平衡因子为0时不需要调整直接返回:
平衡因子为1或者-1时需要向上调整
平衡因子为2或者-2时需要旋转:
if(parent.bf == 0) {
//说明已经平衡了
break;
}else if(parent.bf == 1 || parent.bf == -1) {
//继续向上去修改平衡因子
cur = parent;
parent = cur.parent;
}else {
if(parent.bf == 2) {
//右树高-》需要降低右树的高度
if(cur.bf == 1) {
//左旋
rotateLeft(parent);
}else {
//cur.bf == -1
rotateRL(parent);
}
}else {
//parent.bf == -2 左树高-》需要降低左树的高度
if(cur.bf == -1) {
//右旋
rotateRight(parent);
}else {
//cur.bf == 1
rotateLR(parent);
}
}
//上述代码走完就平衡了
break;
四.AVL树的旋转
1.右旋
由于60节点的平衡因子为-2所以不满足AVL树的定义,需要对该节点进行旋转。左树比右树高,所以需要减少左树的高度增加右树的高度,简单的来说就是把30节点提上去,60节点压下来。为了完成该操作首先让60节点的左树链接40节点,然后30节点右边链接60节点把60节点压下来,在更改30节点和60节点的parent指针的指向。旋转后整棵树平衡,旋转前30节点和60节点的bf分别为-1和-2,旋转后树平衡了,所以还需要改30节点和60节点的bf为0。
代码实现:
private void rotateRight(TreeNode parent) {
TreeNode subL = parent.left;
TreeNode subLR = subL.right;
parent.left = subLR;
subL.right = paren