栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

6.3 AVL树

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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。其旋转调整方法,是将左子树作为新的根节点,旧的根节点作为右节点。调整为下图所示状态:

  代码如下:

private void ll() {
    final AVLNode 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是右子树多了个右孩子。

  调整后是这个样子:

  代码如下:

private void rr() {
    final AVLNode 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型的旋转方法:

private void lr() {
    this.getLeft().rr();
    this.ll();
}
RL型旋转

  RL是右子树多了个左孩子。如图

  旋转后:

  旋转的代码:

private void rl() {
    this.getRight().ll();
    this.rr();
}
AVL树的插入

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

    protected OrderBinaryNode innerAdd(OrderBinaryNode newNode) {
        OrderBinaryNode 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 avlNode) {
    // 往上找直到子树不平衡
    for(AVLNode pointer = avlNode;pointer != null; pointer = (AVLNode) pointer.parent) {
        if (pointer.getLeftHeight() - pointer.getRightHeight() > 1) {
            // LL 或 LR
            final AVLNode 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 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 node = findNode(t);
if (node == null) {
    return;
}
// 如果是root,并且为空
if (node == root && root.isEmpty()) {
    root = null;
    return;
}
// 如果是叶子
if (node.isEmpty()) {
    final BinaryNode parent = node.getParent();
    parent.removeChild(node);
    return;
}
// 否则就自己调整了
node.delete();

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

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

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/727965.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号