[Loj.ac-java] AVL Tree - 元老级别的平衡树

本文介绍了AVL树的基本概念,作为平衡二叉搜索树的一种,AVL树在插入和删除节点后会进行相应的平衡操作以保持高度平衡。讨论了AVL树的平衡双旋原理,并提供了相关操作的代码实现,通过维护函数`maintain`确保树的平衡。同时,通过实例分析了可能需要多次旋转的情况,并推荐了一个在线AVL树可视化工具进行动态观察。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:

说明:

https://oi-wiki.org/ds/avl/

来 写 一 个 A V L。

问题链接:https://loj.ac/problem/104

提交记录:

https://loj.ac/submission/902926

输入案例:

传了一份测试数据:https://download.youkuaiyun.com/download/qq_28033719/12709334 (假装)


我的代码:

准备知识

1、以往知识:

二叉搜索树 https://blog.youkuaiyun.com/qq_28033719/article/details/107769968

2、平衡:

AVL树相对二叉搜索树来说,就是多了一步平衡,改变节点的时候都进行平衡

增加:创建新节点之后就进行平衡,删除:删除节点后,先像二叉搜索树一样,找个子节点补上,然后再进行平衡

3、平衡双旋:

因为 AVL 树机制就是发现一个节点的左右节点相对高度差 == 2 就马上进行旋转,那么 旋转时刻 就只有 高度差得到 2 的情况。

为什么要拿孙子作比较,因为下图右旋作分析,首先因为高度差 == 2,然后左旋的话类似,或者孙子节点左边多一个,右边多一个这些情况也一样的。

     * 进行右旋,左边子节点 的
     * 
     * 同向:左边子节点 的 左边 孙子节点 高度 > 右边 孙子节点 高度
     *       o              o
     *      / \   right    / \
     *     o   o   =》    o   o        (平衡了)
     *    / \            /   / \
     *   o   o          o   o   o
     *  /
     * o
     * 
     * 不同向:左边子节点 的 左边 孙子节点 高度 < 右边 孙子节点 高度
     *       o              o
     *      / \   right    / \
     *     o   o   =》    o   o        (不行)
     *    / \                / \
     *   o   o              o   o
     *        \              \
     *         o              o
     * 应该:
     *       o                o             o
     *      / \              / \   right   / \
     *     o   o            o   o    =》  o   o   (可以了)
     *    / \      left    / \           /   / \
     *   o   o      =》   o   o         o   o   o
     *        \          /
     *         o        o

还有就是,AVL 树旋转次数不一定是常数次,完全可以在这个网址进行数据试验:

https://www.cs.usfca.edu/~galles/visualization/AVLtree.html

这个 0001可以再换其他数据,如果这时候删除 0001 排行 3 的数据,就发生两次双旋,这种情况可能会延伸到 N 次。

具体代码

其实就参考 二叉搜索树 模板,然后加多一个 maintain 处理,增加和删除都多一个 maintain,然后其他和 BST 一样。

import java.util.Scanner;

public class AVLTree
{
    class AVLNode {
        int val;
        int num;
        int size;
        int height;
        AVLNode left;
        AVLNode right;
        public AVLNode(int val) {this.val = val; this.height = this.size = this.num = 1;}
    }
    AVLNode root;

    private void pushup(AVLNode r) {
        if(r != null) {
            r.size = r.num
                   + (r.left == null ? 0 : r.left.size)
                   + (r.right == null ? 0 : r.right.size);

            int lh = r.left == null ? 0 : r.left.height;
            int rh = r.right == null ? 0 : r.right.height;
            r.height = lh >= rh ? lh + 1 : rh + 1;
        }
    }

    private void clear (AVLNode r) {
        if(r != null) { r.left = null; r.right = null; }
    }

    public AVLNode insert(AVLNode r, int v) {
        if(r == null) return new AVLNode(v);
        else if(r.val == v) ++ r.num;
        else if(r.val > v) r.left = insert(r.left, v);
        else r.right = insert(r.right, v);
        pushup(r);
        return maintain(r); // 先统计,再进行平衡
    }

    public AVLNode delete(AVLNode r, int v) {
        if(r == null) return r;
        else if(r.val < v) r.right = delete(r.right, v);
        else if(r.val > v) r.left = delete(r.left, v);
        else {
            -- r.num;
            if(r.num == 0) {
                boolean ln = r.left == null, rn = r.right == null;
                if(ln && rn) return null;
                else if(ln) { // 返回右边
                    AVLNode res = r.right; clear(r); return res;
                }
                else if(rn) { // 返回左边
                    AVLNode res = r.left; clear(r); return res;
                }
                else {
                    AVLNode leftMax = deleteLeft(r.left);
                    leftMax.left = leftMax.val == r.left.val ? leftMax.left : r.left;
                    leftMax.right = r.right;
                    clear(r); r = leftMax;
                }
            }
        }
        pushup(r);
        return maintain(r); // 先统计,再进行平衡
    }

    public AVLNode deleteLeft(AVLNode r) {
        if(r.right == null) return r;
        else {
            AVLNode right = deleteLeft(r.right);
            if(r.right.val == right.val) r.right = right.left; // 注意最右节点可能有个左节点
            r.size -= right.num;
            return right;
        }
    }

    /**
     * 进行右旋,左边子节点 的
     *
     * 左边 孙子节点 高度 > 右边 孙子节点 高度
     *       o              o
     *      / \   right    / \
     *     o   o   =》    o   o        (平衡了)
     *    / \            /   / \
     *   o   o          o   o   o
     *  /
     * o
     *
     * 左边 孙子节点 高度 < 右边 孙子节点 高度
     *       o              o
     *      / \   right    / \
     *     o   o   =》    o   o        (不行)
     *    / \                / \
     *   o   o              o   o
     *        \              \
     *         o              o
     * 应该:
     *       o                o             o
     *      / \              / \   right   / \
     *     o   o            o   o    =》  o   o   (可以了)
     *    / \      left    / \           /   / \
     *   o   o      =》   o   o         o   o   o
     *        \         /
     *         o       o
     */
    public AVLNode maintain(AVLNode r) {
        int lh = r.left == null ? 0 : r.left.height;
        int rh = r.right == null ? 0 : r.right.height;
        if(lh - rh == 2) {
            int llh = r.left.left == null ? 0 : r.left.left.height;
            int lrh = r.left.right == null ? 0 : r.left.right.height;
            if(llh >= lrh) return rotate(r, false);
            else { // 双旋
                r.left = rotate(r.left, true);
                return rotate(r, false);
            }
        } else if(lh - rh == -2) {
            int rlh = r.right.left == null ? 0 : r.right.left.height;
            int rrh = r.right.right == null ? 0 : r.right.right.height;
            if(rlh <= rrh) return rotate(r, true);
            else { // 双旋
                r.right = rotate(r.right, false);
                return rotate(r, true);
            }
        }
        return r;
    }

    public AVLNode rotate(AVLNode r, boolean dir) {
        AVLNode temp;
        if(dir) { // true 左旋, false 右旋
            temp = r.right; r.right = temp.left; temp.left = r;
        } else {
            temp = r.left; r.left = temp.right; temp.right = r;
        }
        pushup(r); pushup(temp); // 旋转点和根节点都需要重新统计
        return temp;
    }

    public int find(AVLNode r, int n) {
        if(r == null) return 0;
        int left = r.left == null ? 0 : r.left.size;
        if(left >= n) return find(r.left, n);
        else if(left + r.num < n) return find(r.right, n - left - r.num);
        else return r.val;
    }

    public int rank(AVLNode r, int v) {
        if(r == null) return 1;
        else if(r.val > v) return rank(r.left, v);
        else if(r.val < v) return rank(r.right, v) + r.num + (r.left == null ? 0 : r.left.size);
        else return r.left == null ? 1 : r.left.size + 1;
    }

    public int pre(AVLNode r, int v) {
        if(r == null) return Integer.MIN_VALUE;
        else if(r.val >= v) return pre(r.left, v);
        else return Math.max(r.val, pre(r.right, v));
    }

    public int sub(AVLNode r, int v) {
        if(r == null) return Integer.MAX_VALUE;
        else if(r.val <= v) return sub(r.right, v);
        else return Math.min(r.val, sub(r.left, v));
    }

    private static int CUR = 0;
    private static int PRE = 1;
    private static int TOP = 0;
    private static AVLNode[][] TWOSTACK = new AVLNode[2][(int)Math.pow(2,11)];
    public static StringBuilder builder = new StringBuilder();
    public String printTree(AVLNode r) {
        if(r != null) TWOSTACK[CUR][TOP ++] = r;
        builder.append("\r\n");
        while(TOP != 0) {
            int TOP2 = 0;
            for(int i = 0;i < TOP;i ++) {
                AVLNode temp = TWOSTACK[CUR][i];
                if(temp.left != null) TWOSTACK[PRE][TOP2 ++] = temp.left;
                if(temp.right != null) TWOSTACK[PRE][TOP2 ++] = temp.right;
                builder.append(temp.val + "  ");
            } builder.append("\r\n");
            TOP = TOP2;CUR ^= 1;PRE ^= 1;
        } builder.append("\r\n");
        return builder.toString();
    }

    public static void main(String[] args) {
        AVLTree tree = new AVLTree();
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        while(num -- > 0) {
            int opt = sc.nextInt();
            int v = sc.nextInt();
            switch (opt) {
                case 1 : tree.root = tree.insert(tree.root, v); break;
                case 2 : tree.root = tree.delete(tree.root, v); break;
                case 3 : System.out.println(tree.rank(tree.root, v)); break;
                case 4 : System.out.println(tree.find(tree.root, v)); break;
                case 5 : System.out.println(tree.pre(tree.root, v)); break;
                case 6 : System.out.println(tree.sub(tree.root, v)); break;
            }
            //System.out.print(tree.printTree(tree.root));builder.setLength(0);
            //Thread.sleep(200L);
        }
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值