《征服数据结构》AVL树

摘要:

1,AVL树的介绍

2,AVL树的插入

3,AVL 树的左旋和右旋

4,LL/LR/RR/RL四种类型的调整

5,AVL树的删除

6,AVL树一些的思考

1,AVL树的介绍

AVL树(Adelson-Velsky and Landis Tree)是一种自平衡二叉搜索树,AVL 树得名于它的发明者 G. M. Adelson-Velsky 和 Evgenii Landis。

在 AVL树中任何节点的两个子树高度差小于等于 1 ,所以它也被称为高度平衡树,增加和删除操作需要通过一次或多次旋转来重新达到平衡。

在前面我们讲过二叉搜索树,对于有 n 个节点的二叉搜索树,它的平均查找时间复杂度是 O(logn) ,但如果创建二叉搜索树的时候插入的是一组升序或者降序的数值,就会导致二叉搜索树始终偏向一方,变成类似链表的形状,查找时间复杂度变成了 O(n) ,这个时候我们就可以使用 AVL树。

ef71e8a64f90b6a63cb8b48260431004.png

我们先来看下AVL树的节点类:

Java 代码:

public class TreeNode {// AVL树的节点类
    public int val;// 节点数据。
    public TreeNode left;// 左子树。
    public TreeNode right;// 右子树。
    public int height;// 当前节点高度。

    public TreeNode(int val) {
        this.val = val;
    }
}

C++ 代码:

struct TreeNode {// AVL树的节点类
    int val;// 节点数据。
    TreeNode *left = nullptr;// 左子树。
    TreeNode *right = nullptr;// 右子树。
    int height;// 当前节点高度。
    TreeNode(int val) : val(val) {}
};

为了方便计算,在节点类中添加一个变量 height 表示当前节点的高度,默认叶子节点高度为 1 。如果没有这个变量,每次获取节点高度的时候都需要重新计算一遍,增加了时间复杂度,有了这个变量以后每次使用的时候直接从节点中取即可。

在 AVL树中,每个节点左子树与右子树的高度差称为节点的平衡因子,任一节点的平衡因子只能是 -1 ,0 和 1 ,如果一个节点的平衡因子绝对值大于 1 ,则表示这棵树失去了平衡,不在是 AVL树 。

2,AVL树的插入

AVL树其实就是一棵二叉搜索树,它的查找和我们前面讲的二叉搜索树的查找是一样的,查找操作就不介绍了,我们来看下它的插入操作。因为AVL树是高度平衡的二叉搜索树,所以插入之后还需要在进行调整,防止出现偏向一边的情况。

AVL树的插入和我们前面讲的二叉搜索树的插入类似,只不过AVL树为了保证树的平衡会进行旋转,所以根节点不是固定的,每次插入的时候都需要更新根节点,我们来看下它的代码。

Java 代码:

public TreeNode root;// AVL树的根节点。

// AVL树在插入节点之后为了保持树的平衡,可能会进行旋转,导致根节点不固定。
public void insert(int val) {
    root = add(root, val);
}

// 插入节点。
private TreeNode add(TreeNode node, int val) {
    if (node == null)
        node = new TreeNode(val);
    if (val < node.val) {// 插入到左子树。
        node.left = add(node.left, val);
    } else if (val > node.val) {// 插入到右子树。
        node.right = add(node.right, val);
    }
    return balanceTree(node);// 插入之后对树进行调整。
}

C++ 代码:

TreeNode *root = nullptr;// AVL树的根节点。

// AVL树在插入节点之后为了保持树的平衡,
// 可能会进行旋转,导致根节点不固定。
void insert(int val) {
    root = add(root, val);
}

// 插入节点。
TreeNode *add(TreeNode *node, int val) {
    if (node == nullptr)
        node = new TreeNode(val);
    if (val < node->val) {// 插入到左子树。
        node->left = add(node->left, val);
    } else if (val > node->val) {// 插入到右子树。
        node->right = add(node->right, val);
    }
    return balanceTree(node);// 插入之后对树进行调整。
}

它使用的是递归的实现方式,最后一行 balanceTree 函数是对树进行调整,也就是说它是自下往上的一直到根节点,只要遇到不平衡的节点都会调整,在调整之前我们先来看下AVL树的旋转。

3,AVL 树的左旋和右旋

树的旋转不影响节点的顺序(中序遍历结果),但会改变树的结构,将一个节点上移、一个节点下移。

左旋是逆时针旋转两个节点,将旋转节点的右子节点提升为子树的根节点,而旋转节点则下降为子树根节点的左子节点。左旋可能会导致旋转节点以及它的右子节点高度发生变化,所以旋转之后它俩的高度需要更新一下。

2e905aa6a800a5740bc0ef1ec40416e1.png

我们看到旋转之后中序遍历的结果是不变的,所以旋转之后它还是一棵二叉搜索树。

再来看下右旋,右旋是将旋转节点的左子节点提升为子树的根节点,而旋转节点则下降为子树根节点的右子节点。右旋可能会导致旋转节点以及它的左子节点高度发生变化,所以旋转之后它俩的高度需要更新一下。

c2c947251b690e28fb640b0ed716fb7a.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

数据结构和算法

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

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

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

打赏作者

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

抵扣说明:

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

余额充值