Java实现AVL树(自平衡二叉搜索树)

一、AVL树的概念 AVL树本质上是一棵二叉搜索树,即它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。另外,AVL树每个结点的左右子树的高度之差,即平衡因子的绝对值不超过1,以实现查询操作的时间复杂度从传统二叉搜索树的O(N)降为O(logN)。二、平衡调整 首先,为什么AVL树需要有“自平衡”的功能?其实从A...
摘要由CSDN通过智能技术生成



 

目录

一、AVL树的概念

二、平衡调整

1、自平衡原理

例子一:

例子二:

2、LL旋转

3、RL旋转

4、RR旋转

5、LR旋转

三、插入结点和删除结点

1、插入结点

2、删除结点

四、完整代码

1、AVL树结点

2、AVL树相关方法

3、测试方法


一、AVL树的概念

       AVL树本质上是一棵二叉搜索树,即它或者是一棵空树,或者是具有下列性质的二叉树: 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值; 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值; 它的左、右子树也分别为二叉搜索树。另外,AVL树每个结点的左右子树的高度之差,即平衡因子的绝对值不超过1。



二、平衡调整

        首先,为什么AVL树需要有“自平衡”的功能?其实从AVL树的特点就可以看出来,因为AVL树每个结点的平衡因子的绝对值不超过1,所以在插入结点和删除结点后,要检查整棵树是否仍然平衡,如果不是则进行调整,使其恢复平衡。可以用以下Java代码来表示AVL树的结点:

public class AVLNode {
    public int key; // 结点的键,用于标识结点在AVL树中的位置
    public Object value; // 结点的值,用于存储数据
    public int height; // 结点的高度
    public AVLNode left; // 结点的左指针
    public AVLNode right; // 结点的右指针

    public AVLNode(int key, Object value) {
        this.key = key;
        this.value = value;
        height = 1;
    }
}

其中,结点高度(height)不是固定的,会随着结点数量的变化而变化,以下是更新结点高度的代码:

private void resetHeight(AVLNode avlNode) {
    avlNode.height = Math.max(
            avlNode.left == null ? 0 : avlNode.left.height,
            avlNode.right == null ? 0 : avlNode.right.height
    ) + 1;
}

1、自平衡原理

下面用两个例子来探讨自平衡的实现原理。

例子一:

 

图2.1.1

如图2.1.1所示,可以看出结点1的平衡因子为-2,整棵树处于不平衡状态,如果想让其恢复平衡,需要结点1的右子树“让”一些结点给左子树,因此,不难看出,可以对结点1进行左旋转来达到目的,以下是左旋转步骤:

private AVLNode leftRevolve(AVLNode avlNode) {
    // 先保存avlNode的右子树
    AVLNode right = avlNode.right;

    // avlNode的右指针指向right的左子树
    avlNode.right = right.left;

    // right的左指针指向avlNode
    right.left = avlNode;

    // 先更新avlNode的高度
    resetHeight(avlNode);

    // 再更新right的高度
    resetHeight(right);

    // 返回right作为调整后的root
    return right;
}

经过一次左旋转之后,整棵树变为:

图2.1.2

要注意的是,在旋转完成后,必须先更新结点1的高度,再更新结点3的高度,因为结点3的高度来自于结点1的高度和结点4的高度,而结点4没有参与旋转,因此不需要更新其高度。

例子二:

        

图2.1.3

如图2.1.3所示,与例子1的区别是,结点3的左子树多了结点2。此时结点1的平衡因子为-2,同例子1,也要进行左旋转来达到平衡,以下是旋转结果:

图2.1.4

可以看出,与例子1的旋转结果几乎一致,只不过结点1的右子树多了结点2,这是这行代码完成的:

// avlNode的右指针指向right的左子树
avlNode.right = right.left;

右旋转同理,不再赘述,以下是右旋转的代码:

private AVLNode rightRevolve(AVLNode avlNode) {
    // 保存avlNode的左子树
    AVLNode left = avlNode.left;

    // avlNode的左指针指向left的右子树
    avlNode.left = left.right;

    // left的右指针指向avlNode
    left.right = avlNode;

    // 先更新avlNode的高度
    resetHeight(avlNode);

    // 再更新left的高度
    resetHeight(left);

    // 返回left作为调整后的root
    return left;
}

下面,开始讨论复杂一点的情况,以下图示中以"key-height"的形式展示每个结点。



2、LL旋转

图2.2.1

如图2.2.1所示,可以看出结点4的平衡因子为-3,为不平衡状态,而除了结点4以外,其它所有结点都处于平衡状态,因此,只需要对结点4进行左旋转,以下是旋转结果:

图2.2.2

 可以看出,整棵树已经平衡,实际上只经历了一次左旋转,因此,不要被LL的字面含义所误导。



3、RL旋转

 

图2.3.1

如图2.2.3所示,同样只有结点4不平衡(平衡因子-3),而如果仍然按照LL旋转中的方法,只对结点4进行一次左旋转,会发生什么?

图2.3.2

可以看到,不仅结点4没有恢复平衡(平衡因子-2),还多了一个不平衡的结点8 (平衡因子2),

其实,这也是这行代码导致的:

// avlNode的右指针指向right的左子树
avlNode.right = right.left;

不难看出,只要有一种办法能让结点5和结点7不再拥有共同的父结点,并且父结点属于兄弟关系,就能让整棵树平衡,而这个办法就是RL旋转,顾名思义,就是先右旋转,再左旋转,那么,对于图2.2.3,先将结点8右旋转(

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值