算法02:AVL树

136 阅读16分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第23天,点击查看活动详情

学习恋上数据结构与算法笔记

1.二叉搜索树的复杂度分析

1.1 比较平衡的情况 O(logn)O(logn)

如果是按照 7、4、9、2、5、8、11 的顺序添加节点

image-20210214011320992

添加删除搜索节点的时间复杂度均为 O(logn)O(logn),比较的次数与树的高度有关,树的高度是多少,就会比较多少次,所以时间复杂度也可以视为 O(h)O(h)

1.2极端情况 退化为链表 O(n)O(n)

但是如果是从小到大添加节点。

image-20210214011507142

  • n 比较大时,两者的性能差异比较大

  • 比如 n = 1000000 时,二叉搜索树的最低高度是 20

1.3 退化成链表的另一种情况

删除节点时也可能会导致二叉搜索树退化成链表

image-20210214011720377

删除节点之后的二叉树

image-20210214011803959

综上所述,得到结论:添加、删除节点时,都可能会导致二叉搜索树退化成链表。

提出问题:有没有办法防止二叉搜索树退化成链表?让添加、删除、搜索的复杂度维持在 O(logn)O(logn)

2.平衡

平衡:当节点数量固定时,左右子树的高度越接近,这棵二叉树就越平衡高度越低

第一种二叉树

image-20210214012024068

第二种二叉树

image-20210214012035870

第三种二叉树

image-20210214012054682

第四种二叉树

image-20210214012110871

从上到下,越来越平衡

3.理想平衡

最理想的平衡,就是像完全二叉树、满二叉树那样,高度是最小的

image-20210214012400811

4.如何改进二叉搜索树?

首先,节点的添加、删除顺序是无法限制的【无法决定树改变的顺序】,可以认为是随机的。所以,改进方案是:在节点的添加、删除操作之后,想办法让二叉搜索树恢复平衡减小树的高度

image-20210214012612557

根节点 7 的左子树高度为 2 ,对应的右子树的高度为 4 ,不平衡。

image-20210214012717497

经过调整之后节点 12 变成了节点 10 和节点 14 的父节点,这样调整之后根节点 7 的右子树的高度为 3

image-20210214012907677

调整之后树的整体高度减小了。

如果接着继续调整节点的位置,完全可以达到理想平衡,但是付出的代价可能会比较大。比如调整的次数会比较多,反而增加了时间复杂度。

总结来说,比较合理的改进方案是:用尽量少的调整次数达到适度平衡即可。一棵达到适度平衡的二叉搜索树,可以称之为:平衡二叉搜索树

5.平衡二叉搜索树(Balanced Binary Search Tree)

英文简称为:BBST。经典常见的平衡二叉搜索树有:

  • AVL
    1. Windows NT 内核中广泛使用
  • 红黑树
    1. C++ STL(比如 mapset
    2. JavaTreeMapTreeSetHashMapHashSet
    3. Linux 的进程调度
    4. Nginxtimer 管理

一般也称它们为:自平衡的二叉搜索树(Self-balancing Binary Search Tree)

6.AVL树

AVL 树是最早发明的自平衡二叉搜索树之一 。AVL 取名于两位发明者的名字 G. M. Adelson-VelskyE. M. Landis(来自苏联的科学家)

平衡因子( Balance Factor ):某结点的左右子树的高度差 。

AVL 树的特点:

  • 每个节点的平衡因子只可能是 1、0、-1(绝对值 1≤1,如果超过 1,称之为失衡
  • 每个节点的左右子树高度差不超过 1
  • 搜索、添加、删除的时间复杂度是 O(logn)O(logn)

叶子节点的平衡因子一定是 0

image-20210214013755812

下面这棵树是一棵 AVL 树。

image-20210214013904146

7.平衡对比

输入数据:35, 37, 34, 56, 25, 62, 57, 9, 74, 32, 94, 80, 75, 100, 16, 82

image-20210214014022810

8.简单的继承结构

AVL 树和红黑树就是在 BST 的基础上增加了自平衡的功能,即在添加/删除元素之后做一些调整来保证修改之后的树还是平衡的。

image-20210214014337786

9.添加导致的失衡

往下面这棵子树中添加 13,最坏情况:可能会导致所有祖先节点失衡, 但是父节点、非祖先节点,都不可能失衡

image-20210214015134443

添加了节点 13 之后,节点 14 的平衡因子变成了 2,其父节点 15 的平衡因子变成了2,其父节点 9 的平衡因子变成了 -2,因此最坏情况下节点 9 上面的所有祖先节点都失衡【因为这部分整个高度增加了 1

但是添加位置(本例中为节点 12) 必然不可能失衡,原因是:首先添加之前节点 12 是平衡的:情况①:节点 12 是叶子节点,那么添加之后肯定还是平衡的;情况②:节点 12 左边有一个节点,在 12 右边添加 13 之后对于节点 12 来说平衡因子为 0,还是平衡的。注意不可能存在其他的情况,如果节点 12 右边有元素的话,新加的节点的父节点就不可能是 12 了。

只会导致 13 -> 12 -> 14 -> 15 -> 9 … 这条线上的非父节点失衡,而节点 4,6,8 等节点是绝对不会失衡的

9.1 LL – 右旋转(单旋)

LL 的含义是:表现形式为 Left-Left【失去平衡的节点是它左边的左边的节点导致的】,解决办法是右旋

添加必然发生在最角落里面。n-> node 节点 p -> parent 父节点 g -> grandparent 祖父节点

初始状态:

image-20210214020753958

在节点 n 的左子树上添加一个节点之后,节点 g 失衡:

image-20210214021304995

LL 表示 Left-Left,失去平衡的节点 g 失衡的原因是其左边的左边的节点 n 里面的子节点导致的。如何能够让节点 g 恢复平衡?一般这里的做法是右旋转。

g.left = p.right  先动高的
p.right = g       后动低的
让p成为这棵子树的根节点

image-20210214021653870

可以看到最右边展示的效果经过旋转之后已经是平衡的了。需要注意的是:由于添加之前 T0T1T2T3 就是平衡的,而且新加的节点并不会导致T0T1T2T3发生变化,因此只需要不需要考虑这些位置的失衡。

仍然是一棵二叉搜索树:T0 < n < T1 < p < T2 < g < T3
整棵树都达到平衡

这一次操作不会导致调整后p 的父节点失衡,因为下面是平衡的,上面自然也是平衡的。【状态3与状态1的高度相同,旋转之后整棵子树的高度没有发生变化】

还需要注意维护的内容
T2、p、g 的 parent 属性
先后更新 g、p 的高度
protected static class Node<E> {
    E element;
    Node<E> left;
    Node<E> right;
    Node<E> parent;
    int height;  // 增加一个高度属性,用来计算平衡因子
 
    public Node(E element, Node<E> parent) {
        this.element = element;
        this.parent = parent;
    }
 
    public boolean isLeaf() {
        return left == null && right == null;
    }
 
    public boolean hasTwoChildren() {
        return left != null && right != null;
    }
}

旋转之后 gp 的左右子树发生了变化,比如 g 的左子树变成了 T2p 的右子树变成了 g ,左右子树都发生了变化,这意味着树的高度发生了变化,需要重新计算。注意更新的顺序:必须是先更新 g,再更新 p,原因是旋转之后 g 已经变成了 p 的子节点了。【先更新矮的,再更新高的】

9.2 RR – 左旋转(单旋)

RR 的含义是:表现形式为 Right-Right【失去平衡的节点是它右边的右边的节点导致的】,解决办法是左旋

初始状态:

image-20210214023548958

在节点 n 的右子树上添加一个节点之后,节点 g 失衡:

image-20210214023657864

g.right = p.left
p.left = g
让p成为这棵子树的根节点

image-20210214024910624

仍然是一棵二叉搜索树:T0 < n < T1 < p < T2 < g < T3
整棵树都达到平衡
还需要注意维护的内容
T2、p、g 的 parent 属性
先后更新 g、p 的高度

9.3 LR – RR左旋转,LL右旋转(双旋)

LR 的含义是:表现形式为 Left-Right【失去平衡的节点是它左边的右边的节点导致的】,解决办法是双旋

初始情况:

image-20210214025105469

T1T2 的下面添加一个节点,导致节点 g 失衡:

image-20210214025255342

解决办法是:先对 p 进行 RR 左旋转:

image-20210214025548632

即将 p 的右边指向 n 的左边, n 的左边指向 ppparent 指向 n,即让 n 成为这棵子树的根节点。可以看到又回到了 LL 的情况,对 g 进行右旋转即可。

image-20210214030107823

9.4 RL – LL右旋转,RR左旋转(双旋)

RL 的含义是:表现形式为 Right-Left【失去平衡的节点是它右边的左边的节点导致的】,解决办法是双旋

初始状态:

image-20210214030531891

T1T2 的下面添加一个节点,导致节点 g 失衡:

image-20210214030618836

解决办法是:先对 p 进行 LL 右旋转:

image-20210214031131108

image-20210214031036224

9.5 代码

在添加节点之后调整树的结构

注意这里使用了模板方法的设计模式

BST.java 中添加 afterAdd(Node<E> node) 方法:

/**
* 添加node之后的调整
* 子类实现这个方法 比如在AVL树中
* @param node 新添加的节点
*/
protected void afterAdd(Node<E> node) {
 
}

将这个方法插入到 add() 方法中:

public void add(E element) {
    elementNotNullCheck(element);
 
    if (root == null) {
        root = new Node<>(element, null);
        size++;
        // 新添加节点之后的处理
        afterAdd(root);
        return;
    }
 
    Node<E> node = root;
    Node<E> parent = null;
 
    int cmp = 0;
    while (node != null) {
        cmp = compare(element, node.element);
        parent = node;
        if (cmp > 0) {
            node = node.right;
        } else if (cmp < 0) {
            node = node.left;
        } else {
            node.element = element;
            return;
        }
    }
    Node<E> newNode = new Node<>(element, parent);
    if (cmp > 0) {
        parent.right = newNode;
    } else {
        parent.left = newNode;
    }
    size++;
    // 新添加节点之后的处理
    afterAdd(newNode);
}

在子类 AVLTree.java 中实现这个方法:

/**
* @param node 新添加的节点
*/
@Override
protected void afterAdd(Node<E> node) {
    // 找到所有失衡节点中高度最低的一个节点[最近的失衡父节点],让它恢复平衡即可
    while((node = node.parent) != null) {
        if(node 是否平衡) {
 
        } else {
 
        }
    }
}

判断是否平衡需要计算平衡因子,因此需要给节点增加一个高度的属性,可以考虑添加到 BinaryTree.java 中的 Node 属性中,但是是不合适的,因为一般的二叉树中并不需要一个高度的概念,一般的如二叉搜索树中的节点不需要这个属性【每次new 一个节点就会多一个没用的属性】,只有在 AVL 树中才需要添加。

如下:直接添加在这个静态内部类中是不合适的。

/**
* 结点类型
*
* @param <E>
*/
protected static class Node<E> {
    E element;
    Node<E> left;
    Node<E> right;
    Node<E> parent;
    int height;   // 高度
 
    public Node(E element, Node<E> parent) {
        this.element = element;
        this.parent = parent;
    }
 
    public boolean isLeaf() {
        return left == null && right == null;
    }
 
    public boolean hasTwoChildren() {
        return left != null && right != null;
    }
}

合适的做法是在 AVLTree.java 中再写一个节点类

private static class AVLNode<E> extends Node<E> {
 
    int height;
 
    public AVLNode(E element, Node<E> parent) {
        super(element, parent);
    }
}

但是这样又有问题了,在 BST.java 中实现添加的时候,往树中添加节点的时候使用的还是 new Node<>() 通用的节点(普通二叉树的节点)。解决办法是在 BinaryTree.java 中再提供一个接口给子类:

/**
* 默认情况下返回一个通用的节点
* @param element
* @param parent
* @return
*/
protected Node<E> createNode(E element, Node<E> parent) {
    return new Node<>(element, parent);
}

来到 BST.java 中:

public void add(E element) {
    elementNotNullCheck(element);
 
    if (root == null) {
        // 修改
        root = createNode(element, null);
        size++;
        afterAdd(root);
        return;
    }
 
    Node<E> node = root;
    Node<E> parent = null;
 
    int cmp = 0;
    while (node != null) {
        cmp = compare(element, node.element);
        parent = node;
        if (cmp > 0) {
            node = node.right;
        } else if (cmp < 0) {
            node = node.left;
        } else {
            node.element = element;
            return;
        }
    }
    // 看看插入到父节点的哪个位置
    Node<E> newNode = createNode(element, parent);
    if (cmp > 0) {
        parent.right = newNode;
    } else {
        parent.left = newNode;
    }
    size++;
    afterAdd(newNode);
}

最后在 AVLTree.java 文件中重写 createNode() 方法,这样在调用的时候会优先执行子类中的方法。

@Override
protected Node<E> createNode(E element, Node<E> parent) {
    return new AVLNode<>(element, parent);
}

注意这里也为后面的红黑树埋下了伏笔,即红黑树里面也可以声明自己的节点并创建自己的节点

增加计算平衡因子代码(注意需要强转,因为父类中节点类型是 Node,没有 height 这个属性)

private boolean isBalanced(Node<E> node) {
    // 能够进来说明一定是一个AVLNode,直接强转即可
    return Math.abs(((AVLNode<E>) node).balanceFactor()) <= 1;
}
 
/**
*
* @param <E>
*/
private static class AVLNode<E> extends Node<E> {
 
    int height = 1;  // 创建叶子节点高度 + 1
 
    public AVLNode(E element, Node<E> parent) {
        super(element, parent);
    }
 
    public int balanceFactor() {
        // 将左节点强转为AVLNode<E>类型的节点
        int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
        return leftHeight - rightHeight;
    }
}

存在的问题:如何更新高度。由于新添加的节点一定是叶子节点,叶子节点的高度可以在构造器里面直接指定 int height = 1; ,真正需要维护的是父节点的高度。

每次加入一个新的节点的时候自动更新高度。

最后实现的代码:

/**
* @param node 新添加的节点
*/
@Override
protected void afterAdd(Node<E> node) {
    // 找到所有失衡节点中高度最低的一个节点,让它恢复平衡即可
    while((node = node.parent) != null) {
        // 进入循环的node一定是新加入node的一个parent
        if(isBalanced(node)) {
            // 如果是平衡的,顺便更新高度
            updateHeight(node);
        } else {
            // 恢复平衡
        }
    }
}
 
@Override
protected Node<E> createNode(E element, Node<E> parent) {
    return new AVLNode<>(element, parent);
}
 
private boolean isBalanced(Node<E> node) {
    // 能够进来说明一定是一个AVLNode,直接强转即可
    return Math.abs(((AVLNode<E>) node).balanceFactor()) <= 1;
}
 
// 更新节点高度(封装一层写强转的代码)
private void updateHeight(Node<E> node) {
    ((AVLNode<E>) node).updateHeight();
}
 
/**
*
* @param <E>
*/
private static class AVLNode<E> extends Node<E> {
 
    int height = 1;
 
    public AVLNode(E element, Node<E> parent) {
        super(element, parent);
    }
 
    public int balanceFactor() {
        // 将左节点强转为AVLNode<E>类型的节点
        int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
        return leftHeight - rightHeight;
    }
 
    // 更新自己的高度
    public void updateHeight() {
        int leftHeight = left == null ? 0 : ((AVLNode<E>)left).height;
        int rightHeight = right == null ? 0 : ((AVLNode<E>)right).height;
        height = 1 + Math.max(leftHeight, rightHeight);
    }
}

恢复平衡的逻辑

判断旋转需要判断是左子树还是右子树,判断依据是左右子树中高度更高的子树。

BinaryTree.java 的静态内部类 Node 中增加方法:

/**
* 判断自己是左子树
*/
public boolean isLeftChild() {
    return parent != null && this == parent.left;
}
 
/**
* 判断自己是左子树
*/
public boolean isRightChild() {
    return parent != null && this == parent.right;
}

AVLTree.java 文件中的 AVLNode 类中增加方法:

/**
* 返回左右子树中更高的子树的根节点
*/
public Node<E> tallerChild() {
    int leftHeight = left == null ? 0 : ((AVLNode<E>) left).height;
    int rightHeight = right == null ? 0 : ((AVLNode<E>) right).height;
    if (leftHeight > rightHeight) return left;
    if (leftHeight < rightHeight) return right;
    // 高度一样的时候返回同方向的(我是父节点的哪一边就返回哪一边)
    return isLeftChild() ? left : right;
}

遇到第一个不平衡的节点,恢复平衡。

protected void afterAdd(Node<E> node) {
    // 找到所有失衡节点中高度最低的一个节点,让它恢复平衡即可
    while ((node = node.parent) != null) {
        // 进入循环的node一定是新加入node的一个parent
        if (isBalanced(node)) {
            // 如果是平衡的,顺便更新高度
            updateHeight(node);
        } else {
            // 遇到第一个不平衡的节点,需要恢复平衡
            rebalance(node);
            // 整棵树恢复平衡
            break;
        }
    }
}

恢复平衡(旋转)的具体逻辑如下:

/**
* 恢复平衡
* @param grand 高度最低的那个不平衡节点
*/
public void rebalance(Node<E> grand) {
    // 节点g左右子节点中相对较高的节点  --> p
    Node<E> parent = ((AVLNode<E>)grand).tallerChild();
    // 节点p左右子节点中相对较高的节点  --> n
    Node<E> node = ((AVLNode<E>)grand).tallerChild();
 
    // L
    if(parent.isLeftChild()) {
        // LL
        if(node.isLeftChild()) {
            // 对节点g右旋
            rotateRight(grand);
        } else { // LR
            // 1. p左旋
            rotateLeft(parent);
            // 2. g右旋
            rotateRight(grand);
        }
    } else {  // R
        if(node.isLeftChild()) {  // RL
            // 1. p右旋
            rotateRight(parent);
            // 2. g左旋
            rotateRight(grand);
        } else {  // RR
            rotateLeft(grand);
        }
    }
}
 
/**
* 左旋
*/
private void rotateLeft(Node<E> node) {
 
}
 
/**
* 右旋
*/
private void rotateRight(Node<E> node) {
 
}

10.统一所有旋转操作

image-20210214155338794

注意这里的节点是按照 a,b,c,d,e,f,g 来进行排序的,在进行 LLRRLRRL 这四种操作之后最终得到的平衡树是差不多的,都是 d 作为根节点,d 的左孩子是 b,右孩子是 f,以此类推。因此完全有可能将所有的操作统一。

抽象的关键在于使得 a,b,c,d,e,f,g 为一类节点统一操作。

/**
 * 恢复平衡  --->  统一操作
*
* @param grand 高度最低的那个不平衡节点
*/
public void rebalance(Node<E> grand) {
    // 节点g左右子节点中相对较高的节点  --> p
    Node<E> parent = ((AVLNode<E>) grand).tallerChild();
    // 节点p左右子节点中相对较高的节点  --> n
    Node<E> node = ((AVLNode<E>) parent).tallerChild();
 
    // L
    if (parent.isLeftChild()) {
        // LL
        if (node.isLeftChild()) {
            rotate(grand, node.left, node, node.right, parent, parent.right, grand, grand.right);
        } else { // LR
            rotate(grand, parent.left, parent, node.left, node, node.right, grand, grand.right);
        }
    } else {  // R
        if (node.isLeftChild()) {  // RL
            rotate(grand, grand.left, grand, node.left, node, node.right, parent, parent.right);
        } else {  // RR
            rotate(grand, grand.left, grand, parent.left, parent, node.left, node, node.right);
        }
    }
}
 
/**
* 统一旋转的操作
* @param r 当前子树的根节点
* @param a
* @param b
* @param c
* @param d 成为新的根节点
* @param e
* @param f
* @param g
*/
private void rotate(Node<E> r,
                    Node<E> a, Node<E> b, Node<E> c,
                    Node<E> d,
                    Node<E> e, Node<E> f, Node<E> g) {
    // 让d成为这棵子树的根节点
    d.parent = r.parent;
    if (r.isLeftChild()) {
        r.parent.left = d;
    } else if (r.isRightChild()) {
        r.parent.right = d;
    } else {
        root = d;
    }
 
    // a-b-c a成为b的left,c成为b的right,同时更新parent
    b.left = a;
    if (a != null) {
        a.parent = b;
    }
    b.right = c;
    if (c != null) {
        c.parent = b;
    }
    // 更新b的高度
    updateHeight(b);
 
    // e-f-g
    f.left = e;
    if(e != null) {
        e.parent = f;
    }
    f.right = g;
    if(g != null) {
        g.parent = f;
    }
    updateHeight(f);
 
    // d-b-f
    d.left = b;
    d.right = f;
    b.parent = d;
    f.parent = d;
    updateHeight(d);
}

11.删除导致的失衡

示例:删除下面这棵子树中的 16

image-20210214162807170

删除节点 16 之后导致节点 15 失衡。

image-20210214163139870

注意:只可能会导致父节点失衡,除父节点以外的其他节点,都不可能失衡

11.1 LL - 左旋转(单旋)

删除之前

image-20210214163353978

删除红色节点之后节点 g 失衡

image-20210214163439388

由于是 LL 的情况:

image-20210214163540727

旋转之后结果如下,节点 n、p、g 都是平衡的。

image-20210214163620891

image-20210214163853142

由于旋转完之后整棵树的高度没有发生变化。

如果绿色节点不存在,更高层的祖先节点可能也会失衡,需要再次恢复平衡,然后又可能导致更高层的祖先节点失衡,极端情况下,所有祖先节点都需要进行恢复平衡的操作,共 lognlogn次调整

image-20210214164041913

怎么理解这个问题呢?试想上图以 g 为根节点的子树是某一个子树 s 的右子树,并且子树 s 的左子树 l 比以 g 为根节点的右子树高度大 1,那么以 g 为根节点的子树在平衡之后高度 减1,那么这就会造成子树 s 失衡。

11.2 LL - 右旋转(单旋)

删除之前

image-20210214164621323

删除红色节点之后节点 g 失衡

image-20210214164659094

image-20210214164739864

如果绿色的节点不存在

image-20210214164828924

11.3 LR-RR左旋转,LL右旋转(双旋)

image-20210214164956514

删除绿色节点之后有可能导致上方失衡

image-20210214165026476

11.4 RL-LL右旋转,RR左旋转(双旋)

image-20210214165227947

删除绿色节点之后有可能导致上方失衡

image-20210214165249118

11.5 解决方案

在删除节点之后做相应的处理

BST.java 文件中增加方法:

/**
* 删除node之后的调整
* 子类实现这个方法 比如在AVL树中
* @param node 被删除的节点
*/
protected void afterRemove(Node<E> node) {
 
}

注意:真正被删除的节点是前驱后继节点,而不是直接传入的那个节点 node

/**
* 真正删除节点
*
* @param node
*/
private void remove(Node<E> node) {
    if (node == null) return;
 
    size--;
 
    if (node.hasTwoChildren()) {
        Node<E> s = successor(node);
        node.element = s.element;
        node = s;
    }
 
    Node<E> replacement = node.left != null ? node.left : node.right;
    if (replacement != null) {
        replacement.parent = node.parent;
        if (node.parent == null) { 
            root = replacement;
        }
        if (node == node.parent.left) {
            node.parent.left = replacement;
        } else {
            node.parent.right = replacement;
        }
 
        // 删完并且更改完指向之后才执行
        // 删除节点之后的处理
        afterRemove(node);
    } else if (node.parent == null) {
        root = null;
 
        // 删除节点之后的处理
        afterRemove(node);
    } else {
        if (node == node.parent.left) {
            node.parent.left = null;
        } else {
            node.parent.right = null;
        }
 
        // 删除节点之后的处理
        afterRemove(node);
    }
}

AVLTree.java 中:

@Override
protected void afterRemove(Node<E> node) {
    // 找到所有失衡节点中高度最低的一个节点,让它恢复平衡即可
    // 被删除节点node的parent属性一直都在
    while ((node = node.parent) != null) {
        if (isBalanced(node)) {
            // 如果是平衡的,顺便更新高度
            updateHeight(node);
        } else {
            // 只要遇到不平衡的节点,一直恢复平衡
            rebalance(node);
        }
    }
}

12.总结

添加

  • 可能会导致所有祖先节点都失衡
  • 只要让高度最低的失衡节点恢复平衡,整棵树就恢复平衡【仅需 O(1)O(1) 次调整】

删除

  • 可能会导致父节点祖先节点失衡 (只有 1 个节点会失衡)
  • 父节点恢复平衡后,可能会导致更高层的祖先节点失衡 【最多需要 O(logn)O(logn) 次调整】

平均时间复杂度

  • 搜索:O(logn)O(logn)
  • 添加:O(logn)O(logn),仅需 O(1)O(1) 次的旋转操作
  • 删除:O(logn)O(logn),最多需要 O(logn)O(logn) 次的旋转操作

13.补充

image-20210214172814735

image-20210214172904215

删除节点 16 的时候会导致节点 15 失衡。虽然父节点失衡了,但是父节点整体的高度是不变的。因此上层的结构肯定也没有失衡,只会导致这一个位置失衡。【删除的是15的比较短的那一边的路径,比较长的那一边并没有发生变化,因此整体的高度并没有发生变化,那么其父节点的平衡因子并没有改变】

image-20210214173102025

删除节点 16 之后,会导致节点 11 (祖父节点) 失衡。

综上所述,得出结论:删除节点 16 的时候,可能会导致父节点祖先节点失衡(只有 1 个节点会失衡),其他节点,都不可能失衡