数据结构之AVL树的 “奥秘“

2024-10-09 15:52:44 浏览数 (6)

二叉树查询性能分析: 插入和删除操作都必须先查找,查找效率代表了二叉搜索树中各个操作的性能 对有n个结点的二叉搜索树,若每个元素查找的概率相等,则二叉搜索在二叉搜索树树平均查找长度是结点的深度的函数,即结点越深,则比较次数越多 如图:

下面就是对二叉搜索树的改进AVL树

目录:

一.AVL树的概念 二.AVL树的实现 三.AVL树的验证 四.AVL树的删除(了解) 五.AVL树的性能分析

一. AVL树的概念:

1. 二叉搜索树虽可以缩短查找的效率,但如果数据有序或接近有序二叉搜索树将退化为单支树,查找元素相当于在顺 序表中搜索元素,效率低下。因此,两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年 发明了一种 解决上述问题的方法:当向二叉搜索树中插入新结点后,如果能保证每个结点的左右子树高度之差的绝对值不超过 1(需要对树中的结点旋转),即可降低树的高度,从而减少平均搜索长 2. 这个我们要定义一个平衡因子,平衡因子 = 右树高度 - 左树高度。 3. 如果一棵二叉搜索树是高度平衡的,它就是AVL树。如果它有n个结点,其高度可保持在 搜索时间复杂 度(Log2^N)。




二 AVL树的实现:

1. 为了AVL树实现简单,AVL树节点在定义时维护一个平衡因子,具体节点定义如下:

代码语言:javascript复制
public class AVLTree {
    static class TreeNode{
        public int val;
        public int bf;//平衡因子
        public TreeNode left;
        public TreeNode right;
        public TreeNode parent;//父亲节点的引用

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

    }
    public TreeNode root;

2.AVL树的插入:

2.1. 按照二叉搜索树的方式插入新节点 2.2. 调整节点的平衡因子 根据平衡因子可以分为几种情况: 情况一:Parent的平衡因子为 0,已经平衡无需调整; 情况二:Parent的平衡因子为 1,-1,继续向上调整; 情况三:Parent的平衡因子为 2,cur的平衡因子为1 ,左单旋(同号) 情况四:Parent的平衡因子为 2,cur的平衡因子为-1 ,右左双旋(异号) 情况五:Parent的平衡因子为 -2,cur的平衡因子为-1 , 右单旋(同号) 情况六:Parent的平衡因子为 -2,cur的平衡因子为 1 , 左右双旋(异号)

代码:

代码语言:javascript复制
  //插入:
    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(cur.val < val){
                parent = cur;
                cur = cur.right;
            }else if(cur.val > val){
                parent = cur;
                cur = cur.left;
            }else {
                return false;
            }
        }

        if(parent.val < val){
            parent.right = node;
        }else{
            parent.left = node;
        }




        node.parent = parent;
        cur = node;//指向新插入的节点

        //修改平衡因子
        while (parent != null) {
            //先看cur在parent的左边还是右边
            if (cur == parent.right) {
                //如果在右边就  
                parent.bf  ;
            } else {
                //如果在左边就--
                parent.bf--;
            }

            //检查当前的平衡因子是不是0,1,-1
            if (parent.bf == 0) {
                //已经平衡了
                break;

            } else if (parent.bf == 1 || parent.bf == -1) {
                //继续向上修改平衡因子
                cur = parent;
                parent = cur.parent;

            } else {
                if(parent.bf == 2){//右树高-》降低右數高度
                    if(cur.bf == 1){
                        //左单旋
                        rotateLeft(parent);
                    }else {
                        //cur.bf == -1
                        //右左双旋:
                        rotateRL(parent);

                    }
                }else {
                    //parent.bf == -2//左树高-》降低左树高度
                    if(cur.bf == -1){
                        //右单旋
                        rotateRight(parent);
                    }else {
                        //cur.bf == 1
                        //左右双旋:
                        rotateLR(parent);
                    }
                }
                //已经平衡
                break;
            }
        }
        return true;
    }

3.AVL树的旋转:

3.1. 新节点插入较高左子树的左侧---右单旋 : Parent的平衡因子为 -2,cur的平衡因子为-1 , 右单旋(同号) 图解:

代码:

代码语言:javascript复制
 /**
     * 右单旋
     * @param parent
     */
    private void rotateRight(TreeNode parent){
        TreeNode subL = parent.left;
        TreeNode subRL = subL.right;

        parent.left = subRL;
        subL.right = parent;
        //如果旋转的整棵树也是一个子树,记录下原来该树的父亲,后续修改
        TreeNode pParent = parent.parent;

        if (subRL != null) {
            subRL.parent = parent;
        }
        parent.parent = subL;

        //看看整棵树是否也是一个子树
        if(parent == root){
            root = subL;
            root.parent = null;
        }else {
            //是子树就确定这棵树是左子树还是右子树
            if(pParent.left == parent){
                pParent.left = subL;
            }else {
                pParent.right = subL;
            }
        }

        subL.parent = pParent;

        //修改平衡因子
        subL.bf = 0;
        parent.bf = 0;
    }

3.2. 新节点插入较高右子树的右侧---左单旋: Parent的平衡因子为 2,cur的平衡因子为1 ,左单旋(同号) 图解:

代码:

代码语言:javascript复制
/**
     * 左单旋
     * @param parent
     */
    private void rotateLeft(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;

        parent.right = subRL;
        subR.left = parent;
        TreeNode pParent = parent.parent;

        if(subRL != null){
            subRL.parent = parent;
        }
        parent.parent = subR;

        //看看整棵树是否也是一个子树
        if(parent == root){
            root = subR;
            root.parent = null;
        }else {
            //是子树就确定这棵树是左子树还是右子树
            if(pParent.left == parent){
                pParent.left = subR;
            }else {
                pParent.right = subR;
            }
        }
        subR.parent = pParent;

        parent.bf = 0;
        subR.bf = 0;
    }

3.3. Parent的平衡因子为 -2,cur的平衡因子为 1 , 左右双旋(异号): 图解:subLR的平衡因子区分情况 情况一:subLR平衡因子等于-1

情况二:subLR平衡因子等于1

代码:

代码语言:javascript复制
 /**
     *左右双旋:
     * @param parent
     */
    private void rotateLR(TreeNode parent) {
        TreeNode subL = parent.left;
        TreeNode subLR = subL.right;
        int bf = subLR.bf;

        rotateLeft(parent.left);
        rotateRight(parent);


        //双旋时subLR的值-1,1,会有2种插入方式
        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;
        }
    }

4. Parent的平衡因子为 2,cur的平衡因子为-1 ,右左双旋(异号): 图解: 情况一:subRL平衡因子等于1

情况二:subRL平衡因子等于-1

代码:

代码语言:javascript复制
  /**
     *右左双旋:
     * @param parent
     */
    private void rotateRL(TreeNode parent) {
        TreeNode subR = parent.right;
        TreeNode subRL = subR.left;
        int bf = subRL.bf;

        rotateRight(parent.right);
        rotateLeft(parent);


        //双旋时subRL的值-1,1,会有2种插入方式
        if(bf == 1){
            parent.bf = -1;
            subRL.bf = 0;
            subR.bf = 0;
        }else if(bf == -1){
            parent.bf = 0;
            subRL.bf = 0;
            subR.bf = 1;
        }
    }

三.AVL树的验证:

分两步:

1. 验证其为二叉搜索树:

代码:

代码语言:javascript复制
//中序遍历:
    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 leftH = height(root.left);
        int rightH = height(root.right);

        return Math.max(leftH,rightH)   1;
    }

2. 验证其为平衡树:

代码语言:javascript复制
 //判断树是否为平衡二叉树
    public boolean isBalanced(TreeNode root){
        if (root == null) return true;


        int leftH = height(root.left);
        int rightH = height(root.right);

        /**检查这棵树平衡因子是否本身就出错
         * 因为要验证的这棵树的平衡因子,是我们自己定义的,防止出错。
         */

        if(rightH-leftH != root.bf){
            System.out.println("这个节点"   root.val  "平衡因子异常");
            return false;
        }

        return Math.abs(leftH-rightH) < 2
                && isBalanced(root.left)
                && isBalanced(root.right);
    }

四.AVL树的删除(了解):

1、找到需要删除的节点 2、按照搜索树的删除规则删除节点--参考Map和Set及哈希--的奥秘(详解)-CSDN博客中二叉搜索树的删除 3、更新平衡因子,如果出现了不平衡,进行旋转。--单旋,双旋

五.AVL树性能分析:

AVL树是一棵绝对平衡的二叉搜索树,其要求每个节点的左右子树高度差的绝对值都不超过1,这样可以保证查询 时高效的时间复杂度,即 。但是如果要对AVL树做一些结构修改的操作,性能非常低下,比如:插入时要 维护其绝对平衡,旋转的次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。因此:如果需要 一种查询高效且有序的数据结构,而且数据的个数为静态的(即不会改变),可以考虑AVL树,但一个结构经常修 改,就不太适合

1 人点赞