优先级队列(堆)

目录

一、概念

二、PriorityQueue的介绍

2.1 关于PriorityQueue的使用要注意

2.2 PriorityQueue常用接口介绍

三、优先级队列Top-K问题

四、 模拟实现优先级队列


一、概念

        队列是一种先进先出(FIFO)的数据结构,但是在某些情况下,操作可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该场景下,使用队列显然不合适,例如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话。

        在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象 。这种数据结构就是优先级队列 (Priority Queue)

二、PriorityQueue的介绍

2.1 关于PriorityQueue的使用要注意

1.、 使用时必须导入 PriorityQueue 所在的包,即:
import java.util.PriorityQueue;

2、PriorityQueue中放置的元素必须要能够比较大小,不能插入无法比较大小的对象,否则会抛出       ClassCastException异常;

import java.util.PriorityQueue;
import java.util.Queue;

class Student {
    public String name;
    public int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
public class Test {
    public static void main(String[] args) {
        Queue<Student> heap = new PriorityQueue<>();
        heap.offer(new Student("zhangsan", 25));
        heap.offer(new Student("wangwu", 18));
    }
}

 3. 不能插入null对象,否则会抛出NullPointerException;

        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();
        priorityQueue.offer(null);

4、没有容量限制,可以插入任意多个元素,其内部可以自动扩容;

5、插入和删除元素的时间复杂度为O(logN);

6. PriorityQueue底层使用了堆数据结;

7. PriorityQueue 默认情况下是小堆 --- 即每次获取到的元素都是最小的元素

2.2 PriorityQueue常用接口介绍

1、优先级队列的构造方法

三、优先级队列Top-K问题

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

      1. 用数据集合中前K个元素来建堆

前k个最大的元素,则建小堆

前k个最小的元素,则建大堆

      2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

为什么建小堆可以求出前k个最大元素呢?(求大根堆同理)

我们可以这样来理解,最开始我们拿数组的前k个元素建立成小堆,那么此时堆顶元素一定是前k个元素中的最小值数组,那此时我们剩下的元素与堆顶元素比较时,如果比堆顶元素还小,那么它一定不是前k个中的最大值,当数组元素大于堆顶元素时,这个值可能是要求的最大值,我们删除堆顶元素添加这个值,重新调整为小根堆,重复上述操作,最后小根堆里面就是我们要求的最大值.

class Solution {
    public int[] smallestK(int[] arr, int k) {
        if(k == 0) {
            return new int[k];
        }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        for (int i = 0; i < arr.length; i++) {
            if(maxHeap.size() < k) {
                maxHeap.offer(arr[i]);
            } else {
                int top = maxHeap.peek();
                if(top > arr[i]) {
                    maxHeap.poll();
                    maxHeap.offer(arr[i]);
                }
            }
        }
        int[] ret = new int[k];
        for (int i = 0; i < k; i++) {
            int val = maxHeap.poll();
            ret[i] = val;
        }
        return ret;
    }
}

四、 模拟实现优先级队列

1、向下调整建立大根堆

    public void createHeap() {
        for (int parent = (usedSize - 1 - 1) / 2; parent >= 0; parent--) {
            shfitDowm(parent, usedSize);
        }
    }
    private void shfitDowm(int parent, int len) {
        int child = parent * 2 + 1;
        while (child < len) {
            if(child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            //child的下标一定是最大值的下标
            if(elem[child] > elem[parent]) {
                swap(elem, parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    private void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

 

向下调整建立大根堆的时间复杂度:以满二叉树为例,第一层需向下调整h-1层,第二层需向下调整h-2层,第三层需向下调整h-3层……倒数第二层需向下调整1层,最后一层需向下调整0层

空间复杂度:没有申请额外的空间,故空间复杂度为O(1)

2、堆的插入

思想:插入元素的下一个位置,然后再向上调整,使整体成为一个大根堆。

    public void offer(int val) {
        if(isFull()) {
            //扩容
            this.elem = Arrays.copyOf(elem, elem.length * 2);
        }
        elem[usedSize++] = val;
        shfitUp(usedSize - 1);
    }
    private void shfitUp(int child) {
        int parent = (child - 1) / 2;
        while (child > 0) {
            if(elem[child] > elem[parent]) {
                swap(elem, parent, child);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }
    private void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

3、删除一个元素

思想:先让堆顶元素和最后一个元素进行交换,再将堆顶元素进行向下调整,使成为一个大根堆。

    public void poll() {
        if (isEmpty()) {
            throw new EmptyException("没有元素啦!");
        }
        swap(elem,0,usedSize - 1);
        usedSize--;
        shfitDowm(0,usedSize);
    }

    private boolean isEmpty() {
        return usedSize == 0;
    }

4、堆排序

思想:大根堆堆顶元素一定是最大的,先将堆顶元素和最后一个元素交换,然后将堆顶元素进行向下调整,首先定义end = usedSize - 1,也就是计算最后一个元素的下标,使其与堆顶元素进行交换,此时该元素就到了堆顶,然后让该元素向下调整,一直进行循环,让其

end--,循环结束的条件为end > 0,因为只有一个元素时就不用进行向下调整了,本来就是有序的。

    public void heapSort() {
        int end = usedSize - 1;
        while (end > 0) {
            swap(elem, 0, end);
            shfitDowm(0,end);
            end--;
        }
    }
    private void shfitDowm(int parent, int len) {
        int child = parent * 2 + 1;
        while (child < len) {
            if(child + 1 < len && elem[child] < elem[child + 1]) {
                child++;
            }
            //child的下标一定是最大值的下标
            if(elem[child] > elem[parent]) {
                swap(elem, parent, child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }
    private void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值