【HBZ分享】java的大顶堆与小顶堆

大顶堆

  1. 大顶堆属于完全二叉树的一种
  2. 大顶堆是父节点一定 大于 子节点
  3. 左右两个子节点没有顺序要求,左字节点大也行,有子节点大也可以
  4. 二叉堆就是我们所说的大顶堆 或 小顶堆

小顶堆

  1. 小顶堆也是完全二叉树
  2. 小顶堆是父节点一定 小于 子节点
  3. 左右两个子节点没有顺序要求,左字节点大也行,有子节点大也可以
  4. 二叉堆就是我们所说的大顶堆 或 小顶堆

存储原理 及 一些规则

  1. 一般做升序使用【大顶堆】, 做降序使用【小顶堆】,没错,你没看错,就是这样
  2. 堆是一个非线性结构,使用数组来存储完全二叉树非常省空间,可以把堆看作个数组
  3. 一般数组下标为【0】不存储数据,从【1】开始存储,这是为了方便使用
  4. 堆其实就是利用完全二叉树结构来维护数组
  5. 数组中下标为K的节点
    (1). 左子节点下标为【2k】的节点
    (2). 右子节点就是下标为【2
    k+1】的节点
    (3). 父节点就是下标为【k/2】取整的节点, 当想把节点向上移动一层,只需要将当前节点的【下标/2】即可,想向下移动一层,就把当前节点【下标*2】 或 【下标 * 2 + 1】
  6. 注意:数组中存储的数据不是按照大小顺序存的,而是根据大顶堆 或 小顶堆的结构,从父到子, 从左到右一次存储的,所以数组本身是无序的, 如图:

在这里插入图片描述

  1. 公式描述:
    大顶堆: arr[k] >= arr[2k + 1] && arr[k] >= arr[2k]
    小顶堆: arr[k] <= arr[2k + 1] && arr[k] <= arr[2k]

使用场景

  1. 优先级队列:比如MQ产品
  2. 高精度定时器:比如定时任务
  3. TopK问题:海量数据中取得最大值,最小值场景

堆中元素的上下移动 与 插入

  1. 当想把节点向上移动一层,只需要将当前节点的【下标/2】即可,想向下移动一层,就把当前节点【下标*2】 或 【下标 * 2 + 1】

  2. 插入:比如插入个101, 则开始101会放在最后,然后用101和他的父节点进行比较,如果不符合大顶堆规则,则就和父节点交换位置,然后继续和父节点对比, 大顶堆规则就是父节点一定要比子节点大,即找到一个父节点大于插入的节点即可,如图所示:
    在这里插入图片描述

大顶堆代码实现

package 大顶堆;

public class Heap {

    /**
     * 存储元素的数组
     */
    private int[] items;


    /**
     * 记录堆中元素个数
     */
    private int num;

    /**
     * 构造方法,初始化数组容量
     * @param capacity
     */
    public Heap(int capacity){
        // 因为第0位元素不存数据,所以容量要+1
        this.items = new int[capacity + 1];
        this.num = 0;
    }


    /**
     * 向堆中插入数据
     * @param value
     */
    public void insert(int value){

        // 新增元素默认会加到数组最后面
        // ++num因为第一个元素不存储数据,所以存的位置要往后移动一位,就要先加1
        items[++num] = value;

        // 进行上浮操作, 把下标传过去即可
        up(num);
    }

    /**
     * 比较父子节点大小大小, item[parent]的元素是否小于item[child]元素的大小
     */
    public boolean childBig(int parent, int child){

        // 如果父节点 < 子节点,则返回true,表示需要交换
        return items[parent] < items[child];

    }

    /**
     * 交换父子节点元素位置
     * @param parent
     * @param child
     */
    public void swap(int parent, int child){

        int temp = items[parent];
        items[parent] = items[child];
        items[child] = temp;

    }


    /**
     * 元素上浮
     *
     * 不断比较节点,直到items[k] < arr[k/2] 即 当前插入值小于父节点为止
     * 条件: 当前节点为k
     * 左右子节点 = items[2*k], items[2*k+1]
     * 父节点 = items[k/2]  即k/2取整
     *
     *
     * @param k  数组的下标
     */
    private void up(int k){

        // 父节点下标是>1的都要比较,等于1就没必要比较了,因为已经是第一位了
        while (k > 1){

            // 比较当前节点和父节点大小, k/2是父节点下标, k是子节点下标
            if(childBig(k/2, k)){

                // 如果子节点 > 父节点,则交换位置
                swap(k/2, k);

            }else{
                // 如果小于,则直接break即可,不需要在比较了,因为比父节点小,那一定也比爷爷节点小
                break;
            }

            // 当前节点要往上一层,则节点下标要更改为父节点的,然后循环继续和爷爷节点比较
            k = k / 2;

        }
    }

    /**
     * 元素下沉
     *
     * 步骤
     *      先判断他是否有子节点,即判断2*k < num, 第一波k = 1, num是最大元素个数
     *      如果存在2 * k的位置则表示存在子节点,此时再看看是否存在右节点,即2 * k + 1 < num
     *      如果存在右节点,则比较左右节点,并选出大的那个来和k节点进行比较
     *      如果子节点 大于 父节点k, 则需要将父节点 和 子节点交换位置,即下沉
     *      如此反复比较,知道k没有子节点为止
     * @param k
     */
    private void down(int k){

        // 存在子节点时再进行while循环
        while (2 * k < num){

            // 存储左右子节点大的那个值的【下标】
            int maxValue;
            
            // 判断是否存在右节点
            if(2 * k + 1 < num){
                // 比较左右节点大小,【假设左为父,右为子】
                if(childBig(items[2 * k], items[2 * k + 1])){

                    // 右节点大
                    maxValue = 2 * k + 1;
                }else{
                    // 左节点大
                    maxValue = 2 * k;
                }
            }else{
                // 不存在右子节点,则左节点就是大的值
                maxValue = 2 * k;
            }

            // 把当前k节点和较大的子节点比较
            if(childBig(k, items[maxValue])){
                // 子节点大则交换父子节点位置
                swap(k, maxValue);

                // 当前节点要下沉一层,则节点下标要更改为子节点的,然后循环继续和孙子节点比较
                k = maxValue;
            }else{
                // 子节点小,则不用再比较了,孙子节点一定更小,直接中断循环
                break;
            }

        }

    }


    /**
     * 删除堆中最大元素
     *
     * 流程:
     * 删除下标为1的最大节点,然后把最后一个节点放到下标为1的位置,即最小的先放到最大的位置
     * 将最后一个节点位置值设置为0 或 空,然后元素个数01
     * 用下标为1的新值去和他的两个子节点比较
     *      先判断他是否有子节点,即判断2*k < num, 第一波k = 1, num是最大元素个数
     *      如果存在2 * k的位置则表示存在子节点,此时再看看是否存在右节点,即2 * k + 1 < num
     *      如果存在右节点,则比较左右节点,并选出大的那个来和k节点进行比较
     *      如果子节点 大于 父节点k, 则需要将父节点 和 子节点交换位置,即下沉
     *      如此反复比较,知道k没有子节点为止
     *
     * @return
     */
    public int delMax(){
        // 删除下标为1的最大节点,然后把最后一个节点放到下标为1的位置,即最小的先放到最大的位置
        int maxValue = items[1];

        // 将最后一个节点和第一个节点交换,即把最后一个节点放到下标为1的位置
        swap(1, num);
        // 将最后一个节点设置为0,并将元素总个数-1
        items[num] = 0;
        num--;

        // 通过下沉,重新堆化, 节点k = 1, 所以传1
        down(1);
        return maxValue;
    }
}

大顶堆实战 之 优先级队列,通过权重weight来判断优先

MaxHeapPriorityQuere:

package 大顶堆;


/**
 * 大顶堆实战之优先级队列
 * <T extends Comparable<T>>是应为要用compareTo比较大小,所以需要加上
 */
public class MaxHeapPriorityQueue <T extends Comparable<T>> {


    /**
     * 存储队列元素
     */
    private T[] items;

    /**
     * 记录队列元素个数
     */
    private int num;


    public MaxHeapPriorityQueue(int capacity){

        // 数组下标为0,不存储数据,所以总长度要+1
        this.items = (T[])new Comparable[capacity + 1];
        // 开始队列元素肯定是0
        this.num = 0;
    }


    /**
     * 判断队列元素是否为空,为空就不消费了
     */
    public boolean isEmpty(){
        return num == 0;
    }


    /**
     * 比较父子节点大小大小, item[parent]的元素是否小于item[child]元素的大小
     *
     * items[parent] = 案例中Task类型的对象,用Task对象.compareTo, 也就是在Task类中,必须有compareTo方法
     * 所以Task类里面需要实现Comparable<Task>, 并写比较逻辑
     */
    public boolean childBig(int parent, int child){

        // 如果父节点 < 子节点,则返回true,表示需要交换
        // T泛型比较用compareTo
        return items[parent].compareTo(items[child]) < 0;

    }

    /**
     * 向堆中插入数据
     * @param value
     */
    public void insert(T value){

        // 新增元素默认会加到数组最后面
        // ++num因为第一个元素不存储数据,所以存的位置要往后移动一位,就要先加1
        items[++num] = value;

        // 进行上浮操作, 把下标传过去即可
        up(num);
    }

    /**
     * 交换父子节点元素位置
     * @param parent
     * @param child
     */
    public void swap(int parent, int child){

        T temp = items[parent];
        items[parent] = items[child];
        items[child] = temp;

    }


    /**
     * 元素上浮
     *
     * 不断比较节点,直到items[k] < arr[k/2] 即 当前插入值小于父节点为止
     * 条件: 当前节点为k
     * 左右子节点 = items[2*k], items[2*k+1]
     * 父节点 = items[k/2]  即k/2取整
     *
     *
     * @param k  数组的下标
     */
    private void up(int k){

        // 父节点下标是>1的都要比较,等于1就没必要比较了,因为已经是第一位了
        while (k > 1){

            // 比较当前节点和父节点大小, k/2是父节点下标, k是子节点下标
            if(childBig(k/2, k)){

                // 如果子节点 > 父节点,则交换位置
                swap(k/2, k);

            }else{
                // 如果小于,则直接break即可,不需要在比较了,因为比父节点小,那一定也比爷爷节点小
                break;
            }

            // 当前节点要往上一层,则节点下标要更改为父节点的,然后循环继续和爷爷节点比较
            k = k / 2;

        }
    }

    /**
     * 元素下沉
     *
     * 步骤
     *      先判断他是否有子节点,即判断2*k < num, 第一波k = 1, num是最大元素个数
     *      如果存在2 * k的位置则表示存在子节点,此时再看看是否存在右节点,即2 * k + 1 < num
     *      如果存在右节点,则比较左右节点,并选出大的那个来和k节点进行比较
     *      如果子节点 大于 父节点k, 则需要将父节点 和 子节点交换位置,即下沉
     *      如此反复比较,知道k没有子节点为止
     * @param k
     */
    private void down(int k){

        // 存在子节点时再进行while循环
        while (2 * k < num){

            // 存储左右子节点大的那个值的【下标】
            int maxValue;

            // 判断是否存在右节点
            if(2 * k + 1 < num){
                // 比较左右节点大小,【假设左为父,右为子】
                if(childBig(2 * k, 2 * k + 1)){

                    // 右节点大
                    maxValue = 2 * k + 1;
                }else{
                    // 左节点大
                    maxValue = 2 * k;
                }
            }else{
                // 不存在右子节点,则左节点就是大的值
                maxValue = 2 * k;
            }

            // 把当前k节点和较大的子节点比较
            if(childBig(k, maxValue)){
                // 子节点大则交换父子节点位置
                swap(k, maxValue);

                // 当前节点要下沉一层,则节点下标要更改为子节点的,然后循环继续和孙子节点比较
                k = maxValue;
            }else{
                // 子节点小,则不用再比较了,孙子节点一定更小,直接中断循环
                break;
            }

        }

    }

    /**
     * 删除堆中最大元素
     *
     * 流程:
     * 删除下标为1的最大节点,然后把最后一个节点放到下标为1的位置,即最小的先放到最大的位置
     * 将最后一个节点位置值设置为0 或 空,然后元素个数01
     * 用下标为1的新值去和他的两个子节点比较
     *      先判断他是否有子节点,即判断2*k < num, 第一波k = 1, num是最大元素个数
     *      如果存在2 * k的位置则表示存在子节点,此时再看看是否存在右节点,即2 * k + 1 < num
     *      如果存在右节点,则比较左右节点,并选出大的那个来和k节点进行比较
     *      如果子节点 大于 父节点k, 则需要将父节点 和 子节点交换位置,即下沉
     *      如此反复比较,知道k没有子节点为止
     *
     * poll 也叫弹出队列元素,其实就是删除
     *
     * @return
     */
    public T poll(){
        // 删除下标为1的最大节点,然后把最后一个节点放到下标为1的位置,即最小的先放到最大的位置
        T maxValue = items[1];

        // 将最后一个节点和第一个节点交换,即把最后一个节点放到下标为1的位置
        swap(1, num);
        // 将最后一个节点设置为0,并将元素总个数-1
        items[num] = null;
        num--;

        // 通过下沉,重新堆化, 节点k = 1, 所以传1
        down(1);
        return maxValue;
    }

    public static void main(String[] args) {
        MaxHeapPriorityQueue<Task> queue = new MaxHeapPriorityQueue<>(20);
        queue.insert(new Task("任务100", 100));
        queue.insert(new Task("任务20", 20));
        queue.insert(new Task("任务198", 198));
        queue.insert(new Task("任务24", 24));
        queue.insert(new Task("任务66", 66));

        while (!queue.isEmpty()){
            Task poll = queue.poll();
            poll.doTask();
        }
    }
}

Task执行任务的类:

package 大顶堆;


/**
 * 任务对象,该对象会存储到Heap堆中
 */
public class Task implements Comparable<Task>{


    /**
     * 权重优先级,数字越大,优先级越高
     */
    private int weight;

    /**
     * 任务名称
     */
    private String name;


    public Task(String name, int weight){
        this.name = name;
        this.weight = weight;

    }

    /**
     * 执行task
     */
    public void doTask(){
        System.out.println(name + "task运行, 权重 =" + weight);
    }

    /**
     * 重写Comparable,就是写比较逻辑,给Heap中的comparable用
     * @param task the object to be compared.
     * @return
     */
    @Override
    public int compareTo(Task task) {
    	// 调用者里面的weight和要比较的参数weight作比较
    	// this.weight是调用compareTo中的weight
    	// task.weight是传入参数的weight,即被比较者的
        return this.weight - task.weight;
    }
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值