数据结构 AVL树概念以及实现插入的功能(含Java代码实现)

简介: 数据结构 AVL树概念以及实现插入的功能(含Java代码实现)

为啥要有avl树

avl树是在二叉搜索树下的一种进阶形式,是为了防止二叉搜索树在极端情况下产生的链表化的场景,从而在二叉搜索树的基础上,加上了某些条件来阻止这种极端情况的产生,但不是保证完全平衡,而是放开了一定的条件,使得这种情况不那么难以满足.(条件:左右子树的高度差的绝对值不大于1) ,我们在发现大于1的时候可以使用左右旋转的方式来调整数的形态,从而保证了查找的时候有近似于O(logN)的性能.

缺点:

当然,有得必有失,这样也带来了一定的损耗:浪费了空间来保存新的变量,每次插入都判断是否满足条件,这样导致了插入的效率变低,这也使得这种二叉树不适合连续多次的插入和修改数据.

如果我们需要持续多次的插入数据,那么也有更进一步的数据结构 --- 红黑树

AVL树的定义

平衡因子

定义为每个节点的左子树和右子树的高度差(左减去右,右减去左都可以表示)

本文使用右减左表示高度差的定义

例如空树的高度差就是0,这就代表着平衡,

当高度差达到-1,就表示左子树高了,但是还在允许的范围之间,无需调整

当高度差达到1,就代表右子树高了,也在允许的范围之内

当左右子树的高度差超过这个范围(绝对值大于2的时候),那么我们就得需要实现左右旋转来满足这种结构的查找效率.

节点代码结构

static class TreeNode{
        public int val;
        public int bf;//balance factor 平衡因子
        public TreeNode left;//左孩子
        public TreeNode right;//右孩子
        public TreeNode parent;//父节点
        public TreeNode(int val) {
            this.val = val;
        }
    }

实现插入功能

总体思路:

1.按照平衡二叉树的比较方式去寻找到应该插入在的节点

2.插入完了修改一下bf(平衡因子)的值

3.判断是否平衡

3.1 bf = 0直接跳出循环即可

3.2 bf = 1 或 bf = -1就继续向上寻找

3.3 bf = 2考虑左旋右旋修改avl树的结构

以下会包含四种失衡以及解决方案

1.LL类型

我们能很容易发现这种不平衡式因为左子树太深而导致的,此时我们要使用右旋来保证结构的正确.

具体步骤就是

1.将subL作为根节点

2.subLR作为parent的左孩子

3.parent作为subL的右孩子

此时我们发现subL和parent的平衡因子都会变成0

2.RR类型

此时我们明显发现右子树的深度要高于左子树两个节点

此时我们就需要进行左旋来调整整体结构的状态

整体步骤与之前类似

1.将subR提出来当根节点

2.subRL作为parent的右子树

3.parent作为subR的左子树

3.RL类型

这个你可以理解是在右子树深的情况下子树是左子树比较深,而前面的方式是左右子树的子树也是呈现同一趋势的.

执行方式

1.先对右子树进行一次右旋,这时候我们发现总体又一次呈现了RR型状态

左旋即可达成平衡的目标

这里如果我们插入的是节点值是55又会有不一样的平衡值

4.LR类型

执行方式也是一样的,我们对左子树进行左旋,再对整体进行右旋即可

这里我直接给出操作形式,其实这里插入的是25也会有另一种结果,在代码中有所体现,读者可自行画图推导

完整avl树插入代码

public class AVLTree {
    static class TreeNode{
        public int val;
        public int bf;//balance factor 平衡因子
        public TreeNode left;//左孩子
        public TreeNode right;//右孩子
        public TreeNode parent;//父节点
        public TreeNode(int val) {
            this.val = val;
        }
    }
    public TreeNode root;//根节点
    public boolean insert(int val){
        TreeNode node = new TreeNode(val);
        if(root == null) {
            root = node;
            return true;
        }
        TreeNode cur = root;
        TreeNode parent = null;
        while(cur != null){
            if(val < cur.val){
                parent = cur;
                cur = cur.left;
            }else if(val == cur.val){
                return false;
            }else{
                parent = cur;
                cur = cur.right;
            }
        }
        if(parent.val>val){
            parent.left = node;
        }else{
            parent.right = node;
        }
        node.parent = parent;
        //调节负载因子
        cur = node;
        //负载因子的修改
        while(parent != null){
            //先看cur是parent的左还是右
            //平衡因子为右 - 左
            if(cur == parent.right){
                //右树高,因子++
                parent.bf++;
            }else{
                //左树高
                parent.bf--;
            }
            //检查平衡因子变化完了是多少
            if(parent.bf == 0){
                break;
            }else if(parent.bf == 1 || parent.bf == -1){
                //继续向上判断修改平衡因子
                //因为等于1和-1只代表当前子树平衡
                cur = parent;
                parent = parent.parent;
            }else{
                //2或者-2的情况,考虑左旋右旋
                if(parent.bf == 2){
                    if(cur.bf == 1){
                        rotateLeft(parent);
                    }else{
                        //-1的情况
                        rotateRL(parent);
                    }
                }else{
                    //-2的情况
                    if(cur.bf == 1){
                        rotateLR(parent);
                    }else{
                        //-1的情况
                        rotateRight(parent);
                    }
                }
                break;
            }
        }
        return true;
    }
   //左单旋
    private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        parent.right = subRL;
        subR.left = parent;
        if(subRL != null){
            subRL.parent = parent;
        }
        TreeNode pParent = parent.parent;
        parent.parent = subR;
        if(root == parent){
            root = subR;
            root.parent = null;
        }else{
            if(pParent.left == parent){
                pParent.left = subR;
            }else{
                pParent.right = subR;
            }
            subR.parent = pParent;
        }
        subR.bf = parent.bf = 0;
    }
    //右单旋
    private void rotateRight(TreeNode parent){
        //画图便于理解
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        parent.left = subLR;
        subL.right = parent;
        if(subLR != null){
            subLR.parent = parent;
        }
        //必须先记录
        TreeNode pParent = parent.parent;
        parent.parent = subL;
        //检查当前是否为根节点
        if(parent == root){
             root = subL;
             root.parent = null;
        }else{
            //此刻的parent也不一定是根节点,还是有左右树的
            if(pParent.left == parent){
                pParent.left = subL;
            }else{
                pParent.right = subL;
            }
            subL.parent = pParent;
        }
        subL.bf = 0;
        parent.bf = 0;
    }
    //左右双旋
    private void rotateLR(TreeNode parent){
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;
        rotateLeft(parent.left);
        rotateRight(parent);
        if(bf == -1){
            subL.bf = 0;
            subLR.bf = 0;
            parent.bf = 1;
        }else if(bf == 1){
            subL.bf = -1;
            subLR.bf = 0;
            parent.bf = 0;
        }
    }
    //右左双旋
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;
        rotateRight(parent.right);
        rotateLeft(parent);
        if(bf == 1){
            parent.bf = -1;
            subR.bf = 0;
            subRL.bf = 0;
        }else if(bf == -1){
            parent.bf = 0;
            subR.bf = 0;
            subRL.bf = 1;
        }
    }
    //验证当前树
    public void inorder(TreeNode root){
        if(root == null){
            return;
        }
        inorder(root.left);
        System.out.println(root.val+" ");
        inorder(root.right);
    }
    private int height(TreeNode root){
        if(root == null){
            return 0;
        }
        int left = height(root.left);
        int right = height(root.right);
        return Math.max(left+1,right+1);
    }
    public boolean isBalanced(TreeNode root){
        if(root == null){
            return true;
        }
        int left = height(root.left);
        int right = height(root.right);
        //判断平衡因子是否有问题
        if (right-left != root.bf){
            System.out.println("这个节点" + root.val +"平衡因子异常");
            return false;
        }
        return Math.abs(left-right)<=1&&
                isBalanced(root.left) && isBalanced(root.right);
    }
    public static void main(String[] args) {
        int[] arr= new int[]{4, 2, 6, 1, 3, 5, 15, 7, 16,14};
        AVLTree avl = new AVLTree();
        for (int i = 0; i < arr.length; i++) {
            avl.insert(arr[i]);
        }
        System.out.println(avl.isBalanced(avl.root));
    }
}
相关文章
|
8天前
|
算法 DataX
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
|
27天前
|
存储 算法 搜索推荐
数据结构-概念版(七)
数据结构-概念版
49 0
|
27天前
|
存储 算法 Serverless
数据结构-概念版(六)
数据结构-概念版
38 0
|
27天前
|
存储 机器学习/深度学习 算法
数据结构-概念版(二)
数据结构-概念版
32 0
|
28天前
|
存储 算法 程序员
【数据结构】【版本2.0】【树形深渊】——二叉树入侵
【数据结构】【版本2.0】【树形深渊】——二叉树入侵
|
29天前
|
存储 算法 数据库
【C/C++ 数据结构 】树的 四种表示方法
【C/C++ 数据结构 】树的 四种表示方法
30 0
|
29天前
|
存储 算法 C语言
【C/C++ 数据结构 树】探索C/C++中的二叉树:从理论到实践
【C/C++ 数据结构 树】探索C/C++中的二叉树:从理论到实践
60 0
|
1天前
|
缓存 Java
【Java基础】简说多线程(上)
【Java基础】简说多线程(上)
5 0
|
1天前
|
并行计算 算法 安全
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
Java从入门到精通:2.1.3深入学习Java核心技术——掌握Java多线程编程
|
1天前
|
安全 Java 编译器
是时候来唠一唠synchronized关键字了,Java多线程的必问考点!
本文简要介绍了Java中的`synchronized`关键字,它是用于保证多线程环境下的同步,解决原子性、可见性和顺序性问题。从JDK1.6开始,synchronized进行了优化,性能得到提升,现在仍可在项目中使用。synchronized有三种用法:修饰实例方法、静态方法和代码块。文章还讨论了synchronized修饰代码块的锁对象、静态与非静态方法调用的互斥性,以及构造方法不能被同步修饰。此外,通过反汇编展示了`synchronized`在方法和代码块上的底层实现,涉及ObjectMonitor和monitorenter/monitorexit指令。
6 0