6.3 AVL树

AVL树特性

  AVL树,之所以叫AVL是因为由三个人发明的。这三个人分别是GM Adelson,Velsky和EM Landis。他们的名字缩写就是AVL。AVL树是一种平衡二叉搜索树,相对红黑树来说比较简单。
  二叉树的意思,就是树只有两个节点,平衡的意思是左边和右边的层数要平衡。也就是层数相差不能超过一,而且每个子树也要是平衡二叉树。搜索的意思是这是个排序树,就是左孩子要比父节点小,并且右孩子比父节点大。如下图就是一颗AVL树:
在这里插入图片描述
  因为我用的是java编程语言实现的AVL树,所以我使用了几级的类继承结构。如下图:
在这里插入图片描述
  首先是树,子类是二叉树,再下面是排序树,再下面是平衡树。这样就做成了一个平衡二叉树。
  当然,除了树还有节点类,我也使用了多级类继承结构。
在这里插入图片描述

平衡二叉树的旋转

  二叉树的核心就是添加或删除完成之后,判断一下树是否平衡,如果不平衡就进行旋转调整。旋转时是值的拷贝,而不是把根换掉。而且旋转时要寻找最小子树。旋转类型分为四种,对应四种不平衡场景,LL LR都属于L开头,意思是左边深度大于右边深度+1,也就是左边深度至少是右边的深度+2。RL和RR同理。下边我细细讲一讲:

LL型旋转

在这里插入图片描述

  LL旋转又叫右旋。是在左子树深度远大于右子树的情况下进行的旋转。意思是左子树加了个左孩子的意思。很明显这是一种不平衡,如图,左子树深度为2,右子树深度为0。其旋转调整方法,是将左子树作为新的根节点,旧的根节点作为右节点。调整为下图所示状态:
在这里插入图片描述
  代码如下:

/**
 *   3    2
 *  2 -> 1 3
 * 1
 * @return
 */
private void ll() {
    final AVLNode<T> newRight = new AVLNode<>(this.value);
    newRight.setRight(this.getRight());
    newRight.setLeft(this.getLeft().getRight());
    this.setRight(newRight);

    this.value = this.getLeft().value;
    this.getLeft().parent = null;
    this.setLeft(this.getLeft().getLeft());
}

RR型旋转

  同理,RR是右子树多了个右孩子。
在这里插入图片描述

  调整后是这个样子:
在这里插入图片描述
  代码如下:

/**
 * 1      2
 *  2 -> 1 3
 *   3
 * @return
 */
private void rr() {
    final AVLNode<T> newLeft = new AVLNode<>(this.value);
    newLeft.setLeft(this.getLeft());
    newLeft.setRight(this.getRight().getLeft());
    this.setLeft(newLeft);

    this.value = this.getRight().value;
    this.getRight().parent = null;
    this.setRight(this.getRight().getRight());
}

LR型旋转

  LR是左子树多了个右孩子。
在这里插入图片描述
  调整后:
在这里插入图片描述
  调整的代码完全可以复用LL旋转和RR旋转的代码,也就是先变成LL型,再执行LL型的旋转方法:

/**
 *    7        3
 *   2    ->  2 7
 *    3
 * @return
 */
private void lr() {
    this.getLeft().rr();
    this.ll();
}

RL型旋转

  RL是右子树多了个左孩子。如图
在这里插入图片描述

  旋转后:
在这里插入图片描述
  旋转的代码:

/**
 * 5       6
 *   7 -> 5 7
 *  6
 * @return
 */
private void rl() {
    this.getRight().ll();
    this.rr();
}

AVL树的插入

  插入首先要按二叉排序树的方法进行插入,也就是在左右子树循环找,进行比较,直到找到合适的位置进行插入,代码代码如下:

/**
     * 内部添加方法
     * @param newNode
     * @return
     */
    protected OrderBinaryNode<T> innerAdd(OrderBinaryNode<T> newNode) {
        OrderBinaryNode<T> node = this;
        T t = newNode.value;
        while (node != null) {
            if (t.compareTo(node.getValue()) > 0) {
                // 如果right == null
                if (node.getRight() == null) {
                    node.setRight(newNode);
                    return node;
                }
                node = node.getRight();
            } else if (t.compareTo(node.getValue()) < 0) {
                if (node.getLeft() == null) {
                    node.setLeft(newNode);
                    return node;
                }
                node = node.getLeft();
            } else {
                throw new DuplicateKeyException(t);
            }
        }
        return null;
    }

  插入之后需要按四大旋转调整节点,寻找最小不平衡子树,并且调整的代码如下:

private void fixToBalance(AVLNode<T> avlNode) {
    // 往上找直到子树不平衡
    for(AVLNode<T> pointer = avlNode;pointer != null; pointer = (AVLNode<T>) pointer.parent) {
        if (pointer.getLeftHeight() - pointer.getRightHeight() > 1) {
            // LL 或 LR
            final AVLNode<T> left = pointer.getLeft();
            if (left.getLeftHeight() > left.getRightHeight()) {
                // LL
                pointer.ll();
                break;
            }

            if (left.getLeftHeight() < left.getRightHeight()) {
                // LR
                // 先子树RR,再整体LL
                pointer.lr();
                break;
            }
        }

        if (pointer.getLeftHeight() - pointer.getRightHeight() < -1) {
            // RR 或 RL
            final AVLNode<T> right = pointer.getRight();
            if (right.getLeftHeight() > right.getRightHeight()) {
                // RL
                pointer.rl();
                break;
            }

            if (right.getLeftHeight() < right.getRightHeight()) {
                // RR
                pointer.rr();
                break;
            }
        }
    }
}

  从代码可以看出是从新加的节点进行向上寻找,然后判断不平衡的类型。

AVL树的删除

  AVL树的删除比添加节点要复杂。
  第一步是删除吧。但是删除之后要符合二叉树的特征。
  比如这棵树
在这里插入图片描述

  要删除6,可以把7代替原来的位置,也可以用5代替原来的位置。但是如果删除的是8呢?只能将7放过来。代替原来的位置。
  具体操作就是一个判断,如果左子树不为空,在左子树一直找右子节点,直到没有了为止。这样就找到了最大值。
  否则,如果右子树不为空,找右子树的最小值。
  在删除之后就进行旋转调整,同样是四种旋转。
  代码如下:

// 排序二叉树的删除
final OrderBinaryNode<T> node = findNode(t);
if (node == null) {
    return;
}
// 如果是root,并且为空
if (node == root && root.isEmpty()) {
    root = null;
    return;
}
// 如果是叶子
if (node.isEmpty()) {
    final BinaryNode<T> parent = node.getParent();
    parent.removeChild(node);
    return;
}
// 否则就自己调整了
node.delete();

  删除但是不调整的代码如下:

public void delete() {
    // 排序树节点,删除自己之后还是个排序树。
    // 这是自己不空的情况
    // 分两种情况
    if (getLeft() != null) {
        // 左节点找出最大值
        OrderBinaryNode<T> node = this.getLeft();
        while (node.getRight() != null) {
            node = node.getRight();
        }
        // 这是最大值,删除了自己
        this.value = node.value;
        node.parent.removeChild(node);
    } else if (getRight() != null) {
        // 右节点找出最小值
        OrderBinaryNode<T> node = this.getRight();
        while (node.getLeft() != null) {
            node = node.getLeft();
        }
        this.value = node.value;
        node.parent.removeChild(node);
    }
}

  而调节平衡调用添加时的调节方法就行了.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醒过来摸鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值