[Loj.ac-java] Treap - 旋转树(树堆 / 平衡树)

问题:

说明:

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

来 写 一 个 Treap。

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

提交记录:

https://loj.ac/submission/860484 

输入案例:

传了一份测试数据:https://download.csdn.net/download/qq_28033719/12609664


我的代码:

知识准备

1、树堆:

是一种平衡树,由二叉搜索树(BST)分化而来,而且结合了堆排序特征(最大堆,最小堆),通过权值把最大或最小权值提升到根节点,那么树进行打结点时候,可以不用形成一条链表的情况,因此操作时间稳定在(Log n),具体有证明的。

2、左旋转 右旋转

左旋转:左手方向旋转,也叫做逆时针旋转,比如根节点3,右节点6, 3 -> 6 旋转就像是逆时针旋转。

左旋
   3              3    6           6
  / \            / \  / \         / \
 2   6     =>   2   4   7  =>    3  7
    / \                         / \
   4   7                       2  4

右旋转:和左旋转相反

右旋反过来
     6            3    6           3
    / \          / \  / \         / \
   3   7   =>   2   4    7  =>   2  6
  / \                              / \
 2  4                             4   7

3、前驱 后继

前驱:小于 target 的最大那个数

后继:大于 target 的最小那个数

4、具体代码:

我看了模板然后自己弄了下,还是弄成对象比较适合,也可以用数组(但数组的逻辑太反人类,我弄了好久没通过)

import java.util.Scanner;

public class Main {

    class TreapNode {
        // 容量记录,该节点下面存放节点数量
        int size;
        // 元素本身值
        int value;
        // 随机值
        int random;
        // 权值一样的节点重复数
        int num;
        // 存子节点
        TreapNode left;
        TreapNode right;
    }
    private static boolean LEFT = true;
    private static boolean RIGHT = false;
    int sum = 0;
    TreapNode ROOT = null;

    // 更新节点数
    public void pushup(TreapNode r) {
        // 左右子节点数 + 自己重复数
        int left = r.left == null ? 0 : r.left.size;
        int right = r.right == null ? 0 : r.right.size;
        r.size = left + right + r.num;
    }

    // true - 左旋转 false - 右旋转
    // 左旋 是 逆时针旋转,以根节点为中心,将右边逆时针转上去
    /**
     * 左旋
     *    3              3    6           6
     *   / \            / \  / \         / \
     *  2   6     =>   2   4   7  =>    3  7
     *     / \                         / \
     *    4   7                       2  4
     * 右旋反过来
     *      6            3    6           3
     *     / \          / \  / \         / \
     *    3   7   =>   2   4    7  =>   2  6
     *   / \                              / \
     *  2  4                             4  7
     */
    public TreapNode rotate(TreapNode r, boolean dir) {
        // 暂存 旋转点
        TreapNode op = dir ? r.right : r.left;

        // 将根连接旋转点枝的对立枝,改为旋转点下面同向枝
        if(dir) r.right = op.left;
        else r.left = op.right;
        // 旋转点的同向枝,变为根节点
        if(dir) op.left = r;
        else op.right = r;
        // 重新统计 r 节点的节点数
        pushup(r);
        // 重新统计 op 节点节点数
        pushup(op);
        return op;
    }

    /**
     * 索引 r 根节点,插入 x值 节点
     * 每次 insert 需要统计一次 size
     * sum 具体没用上,可以暴露一个 size() 的方法,不过不必要
    **/
    public TreapNode insert(TreapNode r, int x) {
        if(r == null) {
            r = new TreapNode();
            ++ sum;
            r.size = r.num = 1;
            r.value = x;
            r.random = (int)(Math.random() * 10000);
            return r;
        }
        // 发现重复
        if(r.value == x) {
            r.num ++;
            r.size ++;
            return r;
        }

        TreapNode d;
        if(x > r.value) {
            r.right = insert(r.right, x);
            d = r.right;
        } else {
            r.left = insert(r.left, x);
            d = r.left;
        }

        if(r.random < d.random) r = rotate(r,x > r.value);
        pushup(r);
        return r;
    }

    public TreapNode delete(TreapNode r, int x) {
        if(r == null) return r;
        // 二分递归
        if(x < r.value) r.left = delete(r.left, x);
        else if(x > r.value) r.right = delete(r.right, x);
        // 命中
        else {
            boolean lf = r.left == null;
            boolean rf = r.right == null;
            if(lf && rf) {
                // 已经末尾节点
                r.num --;
                if(r.num == 0) return null;
            } else if(lf) {
                r = rotate(r,LEFT);
                r.left = delete(r.left, x);
            } else if(rf) {
                r = rotate(r,RIGHT);
                r.right = delete(r.right, x);
            } else {
                boolean dir = r.left.random < r.right.random;
                r = rotate(r,dir);
                if(dir) r.left = delete(r.left, x);
                else r.right = delete(r.right, x);
            }
        }

        // 如果是删除,就需要把所有节点数统计减去 1
        -- r.size;
        return r;
    }

    public int find(TreapNode r, int x) {
        if(r == null) return 0;
        int left = r.left == null ? 0 : r.left.size;

        if(left >= x) return find(r.left, x);
        else if(left + r.num < x)  return find(r.right,x - r.num - left);
        else return r.value;
    }

    /**
     * 前驱,小于x最大数
     * @param r
     * @param x
     * @return
     */
    public int pre(TreapNode r, int x) {
        if(r == null) return Integer.MIN_VALUE;
        if(r.value >= x) return pre(r.left,x);
        else return Math.max(r.value, pre(r.right, x));
    }

    public int suc(TreapNode r, int x) {
        if(r == null) return Integer.MAX_VALUE;
        if(r.value <= x) return suc(r.right, x);
        else return Math.min(suc(r.left, x), r.value);
    }

    public int rank(TreapNode r, int x) {
        if(r == null) return 0;
        else if(r.value == x) return r.left == null ? 1 : r.left.size + 1;
        else if(r.value < x) return r.num + rank(r.right,x) + (r.left == null ? 0 : r.left.size);
        else return rank(r.left, x);
    }

    public static void main(String[] args) {
        Main treap2 = new Main();
        Scanner sc = new Scanner(System.in);
        int nn = sc.nextInt();
        for (int i = 0; i < nn; i++) {
            int opt, x;
            opt = sc.nextInt();
            x = sc.nextInt();
            switch (opt) {
                case 1:
                    treap2.ROOT = treap2.insert(treap2.ROOT, x);
                    break;
                case 2:
                    treap2.ROOT = treap2.delete(treap2.ROOT, x);
                    sum --;
                    break;
                case 3:
                    System.out.println(treap2.rank(treap2.ROOT, x));
                    break;
                case 4:
                    System.out.println(treap2.find(treap2.ROOT, x));
                    break;
                case 5:
                    System.out.println(treap2.pre(treap2.ROOT, x));
                    break;
                case 6:
                    System.out.println(treap2.suc(treap2.ROOT, x));
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_28033719/article/details/107316144